ADC analogno-digitalni pretvornik. Digitalni termometer, LM35 in PIC16f877a

Izdelali bomo merilnik temperature. Izmerjena temperatura naj se prikazuje v stopinjah Celzija (°C) in v stopinjah Fahrenheita (°F) na LCD prikazovalniku 2x16. Za krmiljenje termometra bomo uporabili mikrokontroler PIC16f877a, za merjenje temperature pa temperaturni senzor LM35.

Vsaka fizikalna veličina, ki jo najdemo v naravi, kot so temperatura, vlaga, tlak, sila, je analogna. Če želimo te veličine obdelovati z računalnikom oziroma z mikrokontrolerjem, jo moramo pretvoriti v digitalno. To naredimo z analogno digitalnim pretvornikom. Analogno digitalni pretvornik ali ADC je naprava, ki neprekinjeno pretvarja analogno veličino (v našem primeru napetost) v ustrezne digitalne vrednosti.

Za merjenje temperature bomo uporabili temperaturni senzor LM35. Napetost na izhodu senzorja je linearno odvisna od temperature in se spreminja za 10 mV/°C. Pri temperaturi 25°C znaša izhodna napetost temperaturnega senzorja LM35 250 mV. Z višanjem temperature izhodna napetost senzorja narašča in pri temperaturi 150°C znaša 1500 mV oziroma 1,5 V.

Slika 1: Temperaturni senzor LM35 (vir: http://www.mileti.com.ar/hoy/?p=413)


Mikrokontroler PIC16f877a vsebuje 8 ADC vhodov. Nahajajo se na PORTA od AN0 do AN7 (RA0/AN0 do RA7/AN7).

Izbrali bomo taki način delovanja, da bo spodnja vrednost referenčne analogne napetosti 0 V (Vss), zgornja vrednost referenčne analogne napetosti pa 5 V (Vdd).

Mikrokontroler vhodno vrednost analogne napetosti, ki se spreminja (izhod temperaturnega senzorja), neprestano primerja z referenčno napetostjo. AD pretvornik pa spreminjajočo analogno napetost pretvarja v digitalno vrednost. Digitalna vrednost je pravzaprav število, ki ustreza izmerjeni napetosti, podano je v binarni obliki kot zaporedje enic in ničel.

Slika 2: PIC16f877a, priključki

Mikrokontroler PIC16f877a vsebuje 10-bitni, 8 kanalni ADC. To pomeni, da bo imela izhodna vrednost ADC ločljivost od 0 - 1023 (210 – 1 = 1023). Najmanjša vrednost spremembe analogne napetosti, ki jo 10-bitni ADC zazna je 5 V/1023 = 0,0048876 V oz. 4,8876 mV.

Poglejmo digitalne vrednosti za nekaj primerov:

Za nastavitve in pravilno delovanje modula ADC ima mikrokontroler PIC16f877a namenjene štiri registre:

Tabela 1: Register ADCON0

Tabela 2: Izbira analognega kanala v odvisnosti od nastavitve bitov CHS <2:0>

                                                                                                 

Tabela 3: Register ADCON1

Tabela 4: Delovanje pinov na PORTA v odvisnosti od nastavitve bitov PCFG <3:0> 

Več podatkov najdemo v priloženi dokumentaciji za mikrokontroler PIC16f877a.

Za branje rezultata AD pretvorbe bomo upoštevali naslednje korake:

Inicializacija modula AD pretvorbe:

         ADCON0 = 0b10000001  

         ADCON1= 0b10001110 

ADON = 1 => AD pretvorba je omogočena.

Biti ADCS2, ADCS1 in ADCS0 imajo vrednost 010 => Čas v katerem se izvede AD pretvorba je 32 Tosc = 32 * 0,25 µs = 8 µs (oscilator 4 MHz).

Biti PCFG3, PCFG2, PCFG1, PCFG0 imajo vrednost 1110 => Pin RA0/AN0 je analogni, vsi ostali (RA1/AN1 - RA7/AN7) so digitalni. Vref+ = Vdd, Vref- = Vss.

Rezultat AD pretvorbe:

Rezultat AD pretvorbe se zapiše v registra ADRESH in ADRESL. Oba registra sta 8-bitna, rezultat AD pretvorbe pa je 10-bitni. Mi smo izbrali desno poravnavo rezultata (ADFM = 1), zato bo rezultat AD pretvorbe v osmih bitih registra ADRESL in v prvih dveh bitih registra ADRESH (tabela 5). Najmanj pomemben bit (LSB) je prvi bit (bit 0) registra ADRESL, najbolj pomembni bit (MSB) pa je drugi bit (bit 1) registra ARDESH. Zadnjih šest bitov registra ADRESH (biti 2 - 7) imajo vrednost 0.

Tabela 5: Desna poravnava 10-bitnega rezultata AD pretvorbe

Izhod temperaturnega senzorja LM35 bomo priključili na pin RA0/AN0. Ostale elemente, LCD zaslon, tipko reset, kristalni oscilator in druge elemente pa bomo priključili na mikrokontroler PIC16f877a kot prikazuje načrt (slika 3).

Slika 3: Priklop elementov na mikrokontroler PIC16f877a

Program za digitalni termometer:

 /*

  Merjenje in prikaz temperature na LCD zaslonu.

  Okolje MPLAB IDE v8.92, HI_TECH compiler for PIC10/12/16 MCUs V9.82, oscilator 4 MHz.

  Avtor: Milan Ivič, nov. 2017.

*/

    #include <htc.h>

    #include <pic.h>

    #include <math.h>

//Definiranje priključnih pinov za LCD:

    #define RS RB1

    #define EN RB3

    #define D4 RB4

    #define D5 RB5

    #define D6 RB6

    #define D7 RB7

    #define _XTAL_FREQ 4000000

    __CONFIG(FOSC_XT & WDTE_OFF & PWRTE_ON & CP_OFF & BOREN_ON & LVP_OFF);

    

    #include "lcd.h"           //Vključitev header (zaglavne) datoteke lcd.h

    const unsigned short Posebni_znaki5x8[] = {

    0b01100,0b10010,0b10010,0b01100,0b00000,0b00000,0b00000,0b00000                //Koda za CGRAM, posebni znak za °

    };

    char tempC[] = "00";                 //Deklariranje 1 dimenzionalnega polja za niz spremenljivk tipa char (znaki), enice in desetice

    char tempF[] = "000";                //Deklariranje 1 dimenzionalnega polja, enice, desetice, stotice

    unsigned long temp_vrednost;         //Deklariranje spremenljivke dolžine 32 bitov

    unsigned long rezultat;

    unsigned long rezultatF;

    char a, b;                           //Deklariranje spremenljivk a in b tipa char (dolžina 8 bitov)

    unsigned int j;                      //Deklariranje spremenljivke j dolžine 16 bitov

 void ADC_Init()

 {

    ADCON0 = 0b10000001;                 //AD pretvorba je omogočena, časovni takt za pretvorbo je 32*Tosc

    ADCON1 = 0b10001110;                 //RA0/AN0 je analogni, vsi ostali so digitalni. Vref+ = Vdd, Vref- = Vss

 }

 unsigned int ADC_beri(unsigned char kanal)

 {

    if(kanal > 0)                        //Zanima nas signal na AN0 (na kanalu 0)

    {

        return 0;

    }

    ADCON0 &= 0b11000101;                //Nastavitev ustreznih bitov registra ADCON0

    ADCON1 = 0b10001110;                 //Izbira analognega vhoda AN0 in izbira desne poravnave rezultata 

    __delay_ms(3);                       //Čas potreben za polnjenje kondenzatorja (dokumentacija za PIC16f877a)

    GO_nDONE = 1;

    while(GO_nDONE);

    return ((ADRESH<<8)|ADRESL);         //Vrni se iz funkcije z rezultatom AD pretvorbe (10-bitni rezultat)

 }

 void main()

 {

    TRISB = 0x00;                               //Vsi pini na PORTB so izhodni

    TRISA = 0xFF;                               //Vsi pini na PORTA so vhodni

    LCD4bitni_Init();                           //Za komunikacijo z LCD-jem uporabimo zaglavno datoteko (header file) lcd.h

    LCD4bitni_Clear();

    LCD4bitni_Set_Cursor(1,1);

    LCD4bitni_Pisi_niz("Temperatura:");

    LCD4bitni_Cmd(0x04);                        //Naslov CGRAM za posebni znak (° => stopinja)

    LCD4bitni_Cmd(0x00);

    for (j = 0; j <= 7 ; j++)

    {

        LCD4bitni_Pisi_Znake(Posebni_znaki5x8[j]);          //Preberi in zapiši vseh 8 bytov (od 0 do 7) znaka °

    }

    LCD4bitni_Set_Cursor(2,4);

    LCD4bitni_Pisi_znak(0);                                 //V drugi vrstici (2) na petem mestu (znak 4) zapiši °

    LCD4bitni_Set_Cursor(2,10);

    LCD4bitni_Pisi_znak(0);

    LCD4bitni_Set_Cursor(2,5);

    LCD4bitni_Pisi_niz("C");

    LCD4bitni_Set_Cursor(2,11);

    LCD4bitni_Pisi_niz("F");

    ADC_Init();

    do

    {

        __delay_ms(50);

        temp_vrednost = ADC_beri(0);                            //Klicanje funkcije za branje in AD pretvorbo signala na AN0 (RA1/AN0)

        rezultat = ((temp_vrednost*500)/1023);                  //Iz binarne v decimalno vrednost

        rezultatF = ((rezultat*9)/5 + 32);                      //Iz °C v °F

        a = rezultat%10;                                        //V spremenljivko a se shrani ostanek vrednosti rezultata deljenega z 10

        tempC[1] = a + 48;                                      //Eniška vrednost temperature (48 ASCII => številka 0)

        rezultat = rezultat/10;

        a = rezultat%10;

        tempC[0] = a + 48;                                      //Desetiška vrednost temperature

        b = rezultatF%10;

        tempF[2] = b + 48;                                      //Enice, eniška vrednost temperature v  °F

        rezultatF = rezultatF/10;

        b = rezultatF%10;

        tempF[1] = b + 48;                                      //Desetice, desetiška vrednost temperature v °F

        rezultatF = rezultatF/10;

        b = rezultatF%10;

        tempF[0] = b + 48;                                      //Stotice, stotiška vrednost temperature v °F

        LCD4bitni_Set_Cursor(2,2);

        LCD4bitni_Pisi_niz(tempC);

        LCD4bitni_Set_Cursor(2,8);

        LCD4bitni_Pisi_niz(tempF);

    }

    while(1);

 }

Opis programske kode:

V projekt vključimo zaglavno datoteko lcd.h, ki je enaka kot v vaji Mikrokontroler PIC16f628a in LCD prikazovalnik. Zaglavna datoteka mora biti v mapi projekta. Najdete jo tudi spodaj v prilogi.

V programu smo deklarirali dva 1-dimenzionalna polja, dvomestno polje tempC[ ] in trimestno tempF[ ]. Pri temperaturi 38°C namreč temperatura v °F preseže vrednost 100 (38°C = 100,4°F). Vrednost temperature lahko prikazujemo do 99°C oziroma 210°F.

Z biti PSFG <3:0> smo izbrali AN0 kot analogni vhod in referenčno napetost Vref+ = Vdd, Vref- = Vss. Ker je Vdd = 5 V pomeni, da bo 10-bitni rezultat imel vrednost 1111111111(2) oziroma 1023(10) (0x3FF) pri analogni napetosti 5 V na vhodu AN0. Ker lahko s temperaturnim senzorjem LM35 merimo temperaturo do največ 150°C, bo napetost na AN0 največ 1,5 V. To pomeni, da ne izkoriščamo celotne 10-bitne ločljivosti, saj bo pri temperaturi 150°C 10-bitni rezultat zavzemal vrednost 307(10) (100110011(2)). Ločljivost je zato slabša. Če bi želeli boljše rezultate, bi za Vref+ izbrali npr. napetost +2,5 V, ki bi jo priključili na pin AN3 (uporabili bi na primer Microchip-ov IC MCP1525, ki da na izhodu natančno napetost 2,5 V). V tem primeru bi seveda morali ustrezno spremeniti vrednost bitov PSFG <3:0> registra ADCON1.

-------------------------------------------------------

Po ADC inicializaciji iz glavne zanka kličemo funkcijo za branje (ADC_beri(0)). Rezultat branja AD pretvorbe shranjujemo v spremenljivko temp_vrednost. V funkciji za branje AD pretvorbe najprej izločimo kanale od AN1 do AN7. Zanima nas le kanal AN0, kamor je priključen izhod temperaturnega senzorja LM35. Nato ustrezno določimo vrednost registra ADCON0 in ADCON1. Po preteku časa 3 ms vklopimo bit GO/DONE in s tem sprožimo AD pretvorbo trenutne analogne napetosti na pinu AN0 v digitalno vrednost. Ko je AD pretvorba končana, se bit GO/DONE avtomatsko postavi na 0. Iz funkcije ADC_beri(0) se vrnemo z 10-bitnim rezultatom, ki je poravnan desno:

                                                                            return ((ADRESH<<8) | ADRESL)

Kako pridemo do 10-bitnega rezultata, ki je poravnan desno:

Primer za vrednosti ADRESH = 00000001 in ADRESHL = 10111001:

    ADRESH = 00000001   =>  če ga razširimo v 16-bitni rezultat:  00000000 00000001

    ADRESH << 8  =>  vrednost prestavimo za 8 mest v levo  =>  00000000 00000001 << 8  =>  00000001 00000000

    ADRESL = 10111001  =>  če ga razširimo v 16-bitni rezultat:  00000000 101111001

Potem oba registra z operacijo ALI ( | ) združimo:

                 00000001 00000000

       ALI

                 00000000 10111001

                -------------------------------

rezultat:    00000001 10111001

V prikazanem primeru bi se iz funkcije za branje AD pretvorbe vrnili z 10-bitnim rezultatom 0110111001. Ta rezultat, ki je desno poravnan bi se v našem programu shranil v spremenljivko temp_vrednost.

-------------------------------------------------------------------------------

Izpis temperature na LCD zaslonu:

Vzemimo primer, da znaša temperatura 25°C. Iz funkcije za branje AD pretvorbe se program vrne z rezultatom 51(10) oziroma 0000110011(2). Ta rezultat je v spremenljivki temp_vrednost.

Če je namreč pri temperaturi 150°C vrednost napetosti na AN0 1,5 V oz 1500 mV, je pri temperaturi 25°C ta vrednost 250 mV.

Ker imamo Vref enako 5 V (Vdd => napetostno napajanje mikrokontrolerja, ki je 5 V), bi bila 10-bitna vrednost AD pretvorbe pri napetosti 5 V na pinu AN0 1023(10) oziroma 1111111111(2). Pri napetosti 250 mV (25°C) na pinu AN0 pa je 10-bitna vrednost AD pretvorbe 51(10) oziroma 0000110011(2).

To vrednost spremenimo v decimalno vrednost in jo shranimo v spremenljivko rezultat:

rezultat = ((temp_vrednost*500)/1023)

V spremenljivki rezultat bo pri temperaturi 25°C vrednost 51*500/1023 = 25. To vrednost sedaj delimo z 10 in poglejmo kakšen je ostanek deljenja:

25/10 = 2 in je ostanek 5 (2*10 + 5 = 25).

Vrednost ostanka shranimo v spremenljivko a:

a = rezultat%10

Številko 5 sedaj pretvorimo v znak tako, da ASCII kodi 0, ta je 48, prištejemo 5. ASCII kode od 0 do 9 zaporedno naraščajo. Če torej ASCII kodi 48 prištejemo 5 dobimo 53, ki je ASCII koda za znak 5. Sedaj moramo mikrokontrolerju povedati, na katero mesto LCD zaslona naj izpiše znak 5:

tempC[1] = a + 48

Izpisal se bo na mesto 1 enodimenzionalnega polja, ki smo ga deklarirali na začetku programa (char tempC[ ] = "00"). Nato še vrednost 2 delimo z 10 in pogledamo ostanek deljenja:

2/10 = 0 in je ostanek 2 (0*10 + 2 = 2).

Vrednost ostanka ponovno shranimo v spremenljivko a, in ga spremenimo v znak tako, da znaku 0 (ASCII 48) prištejemo 2.

Ta znak se naj izpiše na mesto 0 (tempC[0] = a + 48) enodimenzionalnega polja, ki smo ga deklarirali na začetku programa (char tempC[ ] = "00").

Enak princip upoštevamo pri izpisu vrednosti temperature v Fahrenheit-ih, le da tu poleg enic in desetic izpisujemo še stotice. Če hočemo pretvoriti temperaturo iz °C v °F moramo vrednost temperature v °C pomnožiti z 9/5 in prišteti 32:

Izdelali smo še lastni znak za stopinjo (°), kot prikazuje slika 4 in v programu določili, na katerih mestih LCD-ja se naj izpisuje.

Slika 4: Kreiranje posebnega znaka za stopinjo (°)