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).
Vref- = 0 V
Vref+ = 5 V
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:
ADCON0 => A/D Control Register 0
ADCON1 => A/D Control Register 1
ADRESL => A/D Result Low register
ADRESH => A/D Result High register
Tabela 1: Register ADCON0
Bit 7 in bit 6, ADCS1 in ADCS0, ta dva bita skupaj z bitom ADCS2 iz registra ADCON1 določata čas, v katerem se izvede AD pretvorba.
Bit 5 - 3, biti CHS2, CHS1 in CHS0 določajo izbiro analognega vhoda.
Tabela 2: Izbira analognega kanala v odvisnosti od nastavitve bitov CHS <2:0>
Bit 2, GO/DONE je kontrolni bit AD pretvorbe. Ko postavimo vrednost tega bita na 1, se sproži analogno digitalna pretvorba. Ko je AD pretvorba končana, se ta bit postavi na 0.
Bit 0, ADON, če je vrednost tega bita 1, je AD pretvorba omogočena, če pa je vrednost tega bita 0, je AD pretvorba onemogočena.
Tabela 3: Register ADCON1
Bit 7, ADFM določa, v kakšni poravnavi bo pretvorjen 10-bitni digitalni rezultat. Če je vrednost tega bita 1, bo poravnava desna, sicer pa leva.
Bit 6, ADCS2 skupaj z bitoma ADCS1 in ADCS0 ki sta v registru ADCON0 določa čas, v katerem se izvede AD pretvorba.
Bit 3 - 0, PCFG3, PCFG2, PCFG1, PCFG0 določajo obnašanje analognih vhodov na PORTA, ali so določeni kot analogni ali kot digitalni. Z ustrezno nastavitvijo teh bitov lahko izbiramo tudi vrednost referenčne napetosti Vref. Ta je lahko Vdd in Vss, lahko pa izbiramo druge vrednosti referenčne napetosti, ki jih določimo na pinih RA3/AN3 in RA2/AN2.
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:
Inicializiramo modul AD pretvorbe.
Izberemo analogni kanal (pin na PORTA).
Zaženemo AD pretvorbo s postavitvijo bita GO/DONE na 1.
Počakamo da se postavi bit GO/DONE na 0, takrat je AD pretvorba končana.
Preberemo rezultat AD pretvorbe iz registrov ADRESH in ADRESL.
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 (°)