Krmiljenje kegljišča s PIC16f877a
Vaja prikazuje primer krmiljenja kegljišča. Na LCD zaslonu (2x16) naj bo prikazano število metov, število podrtih kegljev posameznega meta in skupno število podrtih kegljev (slika 1). Uporabili bomo mikrokontroler PIC16f877a, saj bi pri uporabi mikrokontrolerja PIC16f628a zmanjkalo priključkov.
Slika 1: Prikaz podatkov na LCD zaslonu
Stanje kegljev bomo simulirali s tipkami oziroma s stikali. Če posamezni kegelj podremo, je njegovo pripadajoče stikalo v stanju logične 1, v nasprotnem primeru pa v stanju logične 0. Pri vsakem metu ustrezni senzor zazna kroglo. Kolikokrat senzor zazna kroglo, toliko metov je bilo izvedenih. Izvedemo lahko do 99 metov. Po pritisku na tipko reset se podatki izbrišejo in lahko začnemo od začetka.
Mikrokontroler PIC16f877a:
Osrednji del krmilnega vezja je mikrokontroler PIC16F877a (katalog je spodaj v prilogi). Njegove glavne značilnosti so:
Je Microchipov mikrokontroler v PDIP ohišju z 40 priključki.
Priključki so razporejeni na PORTA, PORTB, PORTC, PORTD in PORTE in jih lahko programsko krmilimo.
Napetost napajanja Vdd je 5 V.
Vsebuje 8 k flash programskega pomnilnika, 368 bajtov RAM pomnilnika in 256 bajtov podatkovnega EEPROM pomnilnika.
Centralno procesna enota je 8 bitna.
Vsebuje tri časovnike (timerje) in pozna 15 vrst prekinitev.
Maksimalna frekvenca zunanjega oscilatorja je 20 MHz.
Slika 2: Mikrokontroler PIC16f877a
Podatkovni pomnilnik RAM (bralno-pisalni pomnilnik) je razdeljen na štiri banke: banko 0, banko 1, banko 2 in banko 3. Vsebuje SFR (Specisl Function Registers, posebni funkcijski registri) in GPR (General Purpose Registers, registri namenjeni splošni uporabi) registre. SFR registri zavzemajo prvih 32 lokacij v vsaki banki. Preko njih komuniciramo z ostalimi enotami mikrokontrolerja. GPR registri zavzemajo 368 bajtov RAM pomnilnika. Če želimo brati ali pisati v register podatkovnega pomnilnika RAM, se moramo nahajati v banki, v kateri je želeni register. Vsebine GPR registrov, ki so določeni s strani proizvajalca, se izgubijo, ko mikrokontrolerju izklopimo napajanje.
Za izvajanje vseh instrukcij v mikrokontrolerju PIC skrbi 8-bitna aritmetična logična enota ALU (Arithmetic Logic Unit). Lahko sešteva in odšteva 8-bitna števila ter z njimi izvaja nekatere logične operacije (AND, OR, XOR, NOT …). Po izvršitvi vsake instrukcije se v registru STATUS postavijo trije biti na ustrezne vrednosti. Iz njihovih vrednosti lahko ugotovimo, kakšen je dobljeni rezultat. Ta je lahko pozitiven, negativen, enak 0 ali napačen. Te tri bite imenujemo zastavice.
Če želimo pine na PORTA uporabljati kot digitalne vhodno izhodne pine, moramo v registru ADCON1 bite postaviti na vrednost 00000110 (0x06). Pini na PORTA imajo namreč privzeto funkcijo komparatorjev in analogno digitalnih pretvornikov (priložen katalog str. 130).
Slika 3: Priključitev elementov na mikrokontroler PIC16f877a
Stikala, ki simulirajo stanje kegljev so priključena na pine PORTD (RD0 do RD7), eno stikalo pa na pin RA1. Stikalo za simuliranje število metov je priključeno na pin RA0, LCD zaslon pa je priključen na pine PORTB.
Upori R1 do R10 so pull down upori in zagotavljajo na ustreznih pinih stanje logične 0, če pripadajoča stikala niso sklenjena oziroma vklopljena.
Upor R11 je pull up upor in skrbi, da je na reset pinu (MCLR) logična 1 oziroma da se program izvaja. Če sklenemo tipko za reset, pošljemo pinu MCLR logično 0, mikrokontroler je takrat v stanju reset. Ko tipko spustimo, se program začne izvajati od začetka.
Prvi trije priključki LCD zaslona ne smejo biti v zraku, morajo biti ustrezno povezani (priključek 1 na maso, priključek 2 na napetost 5 V, priključek 3 pa na drsnik potenciometra 4,7 k za nastavitev kontrasta LCD-ja). Priključek 5 LCD-ja (R/W) lahko povežemo na pin RB2, lahko pa ga povežemo na maso, saj iz LCD-ja ne bomo nič brali.
Program za krmiljenje kegljišča, assembler:
;-------------------------------------------------------------------------------------------------------------
; Krmiljenje kegljišča z mikrokontrolerjem PIC16f877a in prikaz na LCD zaslonu 2x16
; Okolje MPLAB IDE v8.92, prevajalnik MPASM Assembler V5.51, oscilator 4 MHz.
; Avtor: Milan Ivič, nov 2017
;-------------------------------------------------------------------------------------------------------------
list p=16f877a ;Tip mikrokontrolerja
#include <p16f877a.inc> ;Vključi v program datoteko p16f628a.inc.
__CONFIG _CP_OFF &_WDT_OFF &_PWRTE_ON &_XT_OSC &_LVP_OFF
; Deklaracija spremenljivk:
Cblock 0x20 ;Blok konstant
Temp1 ;Začasna spremenljivka
Temp2 ;Začasna spremenljivka
Naslov ;Nov naslov DDRAM-a
Stevec ;Števec za izpis niza v prvi vrstici
Stevec1 ;Števec za izpis niza v drugi vrstici
Stevec2 ;Števec za izpis na začetek
Stevec3 ;Števec za izpis niza pri številu metov
Podatek1 ;Število metov za izpis na LCD
Nicla ;Indikator ničle za pretvorbo iz 8-bitnega v desetiško
Cifra ;Cifra za izpis
Podreni ;Število podrenih kegljev (naslov 0x2A)
Vsota_podrenih ;Seštevek skupaj podrenih kegljev (naslov 0x2B)
endc ;Konec bloka konstant
; Definicije novih oznak:
#define RS PORTB,1 ;RS LCD linija
#define RW PORTB,2 ;RW LCD linija
#define E PORTB,3 ;E LCD linija
; Definicija konstant za LCD:
DDRAM equ 0x80 ;OR maska za vpis v DDRAM
CGRAM equ 0x48 ;OR maska za vpis v CGRAM
Brisi equ 0x01 ;Brisanje LCD
Home equ 0x02 ;Zaslon v originalno pozicijo
Kurzor equ 0x0C ;Izklop kurzorja
Desno equ 0x1C ;Pomik zaslona v levo
;--------- Začetek programa: ---------
org 0x000
goto Glavni_zacetek
org 0x004
Glavni_zacetek
bcf STATUS,RP1
bsf STATUS,RP0 ;Banka 1
movlw 0x06
movwf ADCON1 ;Pini na PORTA so digitalni
movlw b'00001111' ;RA0, RA1, RA2 in RA3 so vhodi
movwf TRISA
movlw b'11111111'
movwf TRISD ;Vsi pini na PORTD so vhodni
clrf TRISB
movlw b'10000001'
movwf OPTION_REG ;Timer povečuje notranja ura, preddelilnik 1:4 (prekoračenje TMR0 v 1,024ms)
bcf STATUS,RP0 ;Banka 0
clrf PORTB ;Vse pine na PORTB postavimo na 0
clrf PORTD
clrf Vsota_podrenih
movlw .1 ;Po resetu začne šteti mete od 1 naprej
movwf Stevec3
clrf Podatek1 ;Inicializacija
clrf INTCON ;Onemogocimo vse prekinitve, T0IF je 0
call LCD_Init ;Inicializacija LCD-ja
bsf RS ;Podatkovni način, pisanje znakov
call LCD_Niz1 ;Izpišemo niz na Lcd ("Met:") (prva vrstica, na začetku)
clrf Stevec ;Spremenljivka Stevec je nič
movlw 0x07
call DDRAM_n ;Pišemo v prvo vrstico od 8 znaka naprej (začne se z 0)
bsf RS ;Podatkovni način, pisanje znakov
call LCD_Niz2 ;Izpišemo niz na LCD ("Padlo:")
clrf Stevec1
movlw 0x40
call DDRAM_n ;Pišemo na začetek druge vrstice LCD-ja
bsf RS ;Podatkovni način, pisanje znakov
call LCD_Niz3 ;Izpišemo niz na LCD ("Padlo skupaj:")
clrf Stevec2
call Cakaj600ms ;Počakamo cca 600 ms
goto Beri
;************************ Program za ugotavljanje stanja tipk *************************
;***** Preberemo stanje tipke: RA0 registrira št. metov krogle ***********
;***** Zapišemo število meta ***********
Beri
btfss PORTA,0 ;Ali je bila krogla odvržena? (Jo je senzor zaznal?)
goto Beri ;Ne, ni bila odvržena
movlw 0x04 ;Da, piši število metov v prvo vrstico od petega znaka naprej
call DDRAM_n ;Na petem znaku je desetica, na šestem enica (meti od 1 do 99)
movf Stevec3,w ;Stevec3 premaknemo v Podatek1
movwf Podatek1 ;V register Podatek1 vpiši število meta
call Pisi_st1 ;Izpišemo podatek (št. meta) na LCD
incf Stevec3,f ;Stevec3 povečamo za 1
call Cakaj600ms ;Čakamo, da se lahko začne ugotavljati število podrenih kegljev
call Cakaj600ms
;*********** Preberemo stanje podrtih kegljev ***********
Preverjanje_podrenih
clrf Podreni ;Inicializacija (po vsakem metu začne šteti podrte keglje od začetka, od 0)
Preveri_prvi_kegelj
btfss PORTD,0
goto Preveri_drugi_kegelj
incf Podreni,f ;Register Podreni povečuje za 1 za vsak podreni kegelj
Preveri_drugi_kegelj
btfss PORTD,1
goto Preveri_tretji_kegelj
incf Podreni,f ;Register Podreni povečuje za 1 za vsak podreni kegelj
Preveri_tretji_kegelj
btfss PORTD,2
goto Preveri_cetrti_kegelj
incf Podreni,f ;Register Podreni povečuje za 1 za vsak podreni kegelj
Preveri_cetrti_kegelj
btfss PORTD,3
goto Preveri_peti_kegelj
incf Podreni,f ;Register Podreni povečuje za 1 za vsak podreni kegelj
Preveri_peti_kegelj
btfss PORTD,4
goto Preveri_sesti_kegelj
incf Podreni,f ;Register Podreni povečuje za 1 za vsak podreni kegelj
Preveri_sesti_kegelj
btfss PORTD,5
goto Preveri_sedmi_kegelj
incf Podreni,f ;Register Podreni povečuje za 1 za vsak podreni kegelj
Preveri_sedmi_kegelj
btfss PORTD,6
goto Preveri_osmi_kegelj
incf Podreni,f ;Register Podreni povečuje za 1 za vsak podreni kegelj
Preveri_osmi_kegelj
btfss PORTD,7
goto Preveri_deveti_kegelj
incf Podreni,f ;Register Podreni povečuje za 1 za vsak podreni kegelj
Preveri_deveti_kegelj
btfsc PORTA,1
incf Podreni,f ;V registru Podreni so sedaj zabeleženi vsi podreni keglji
call Cakaj600ms
movf Podreni,w
addwf Vsota_podrenih ;Seštevaj podrene keglje po metih, podatek shrani v register Vsota_podrenih
;******** Zapišemo število podrtih kegljev po posameznem metu *********
movlw 0x0D ;Piši število podrenih kegljev po posameznem metu
call DDRAM_n
movf Podreni,w
movwf Podatek1
call Pisi_st
;******** Zapišemo število vseh podrtih kegljev *********
movlw 0x4D ;Piši vsoto vseh podrenih kegljev
call DDRAM_n ;Zapiši v drugo vrstico na naslov 0x4D
movf Vsota_podrenih,w
movwf Podatek1
call Pisi_st ;Število je lahko največ 255 (8-bitni register)
goto Beri ;Idi preverit ali je bila odvržena naslednja krogla
;---------------- Podprogrami: -------------------------
;*******Izpiše vrednost na LCD => 4-bitni način
LCD_pisi
movwf Temp1 ;Podatek iz delovnega registra shranemo v Temp1
movlw 0x0F ;b'00001111' v delovni register
andwf PORTB,f ;Zgornje štiri bite registra PORTB izbrišemo
movf Temp1,w ;Delovnemu registru povrnemo podatek iz Temp1
andlw 0xF0 ;Izbrišemo spodnje štiri bite
iorwf PORTB,f ;Pošljemo zgornje štiri bite podatka
bsf E
bcf E ;Obvestimo LCD o novem podatku
swapf Temp1,f ;Zamenjamo nibble v Temp1
movlw 0xF0 ;b'11110000' v delovni register
andwf Temp1,f ;Spodnje štiri bite v Temp1 izbrišemo
movlw 0x0F ;b'00001111' v delovni register
andwf PORTB,f ;Zgornje štiri bite v registru PORTB izbrišemo
movf Temp1,w ;Podatek iz Temp1 v delovni register
iorwf PORTB,f ;Pošljemo spodnje štiri bite podatka
bsf E ;Potrdimo podatek
bcf E ;Obvestimo LCD o novem podatku
call Cakaj119u
return ;Vrnitev iz podprograma
;********* Zakasnitev 119 mikro sekund ************
Cakaj119u
movlw .38 ;Število ponovitev zanke -> W
movwf Temp2 ;Vrednost delovnega registra v Temp2
Se
decfsz Temp2,f ;Zmanjšaj Temp2 za 1, preskoči naslednjo instrukcijo, če je Temp2=0
goto Se ;Temp2 še ni enak nič
return ;Vrnitev iz podprograma
;*********** Zakasnitev cca 1.03 ms x W ****************
CakajMs
movwf Temp2 ;W v Temp2 (iz LCD_init ima delovni register vrednost 15)
clrf TMR0 ;Vrednost TMR0 postavi na nič
Cak1
btfss INTCON,T0IF ;Ali je TMR0 prekoračil svojo vrednost (.255)?
goto Cak1 ;Zastavica T0IF še ni "1". TMR0 še ni prekoračil vrednosti 255
bcf INTCON,T0IF ;TMR0 je prekoračil vrednost 255, izbriši zastavico T0IF
decfsz Temp2,f ;Zmanjšaj števec ponovitev za 1, rezultat shrani nazaj v Temp2
goto Cak1 ;Temp2 še ni enak nič, nadaljuj z izvajanjem zanke
return ;Temp2=0, vrni se iz podprograma
;********* Zakasnitev cca 600 milisekund ************
Cakaj600ms
movlw .150 ;150 ms v delovni register (4 MHz kristal)
call CakajMs ;Pokliči podprogram CakajMs
call CakajMs
call CakajMs
call CakajMs ;4x150 ms = 600 ms
return ;Vrni se iz podprograma
;****************** Inicializacija LCD ************************
LCD_Init
bcf RW
bcf RS ;RW in RS liniji na 0
movlw .15
call CakajMs ;cca 15ms pavze za LCD
movlw 0x30
movwf PORTB ;Na D7 - D4 pošljemo 0x30
bsf E
bcf E ;Opozorimo LCD na nov podatek
movlw .5
call CakajMs ;cca 5ms pavze
movlw 0x30
movwf PORTB ;Na D7 - D4 pošljemo 0x30
bsf E
bcf E ;Opozorimo LCD na nov podatek
call Cakaj119u ;119 mili sekund pavze
movlw 0x30
movwf PORTB ;Na D7 - D4 pošljemo 0x30
bsf E
bcf E ;Opozorimo LCD na nov podatek
movlw .5
call CakajMs
movlw 0x20
movwf PORTB ;Na D7 - D4 pošljemo 0x20
bsf E
bcf E ;Opozorimo LCD na nov podatek
call Cakaj119u
;Sedaj smo v 4-bitnem načinu
movlw 0x28 ;2x16 znakov, 5x8 znaki, 4-bitno
call LCD_pisi ;Pošljemo 0x28 na LCD
movlw 0x08 ;Izklop zaslona
call LCD_pisi ;Pošljemo 0x08 na LCD
movlw 0x01 ;Brisanje DDRAM-a
call LCD_pisi ;Pošljemo 0x01, izbrišemo zaslon
movlw .2
call CakajMs ;Zahtevana zakasnitev, večja od 2ms
movlw 0x06 ;Pisanje v desno, brez pomikanja zaslona
call LCD_pisi ;Pošljemo 0x06 na LCD
movlw 0x0C ;Vklop zaslona, velik kurzor
call LCD_pisi ;Pošljemo 0x0C na LCD
return ;Konec podprograma
;------- Nastavitev trenutnega naslova DDRAM-a ------------
;************ Naslov vpišemo v W pred klicem podprograma!! *************
DDRAM_n
bcf RS ;Ukazni način
iorlw DDRAM ;Z OR operacijo ustvarimo ukaz
call LCD_pisi ;Vpis ukaza v LCD
bsf RS ;Podatkovni način
return
;--------- Naši nizi, napisani so v tabelah ----------
Tabela1 addwf PCL,f
DT "Met:" ;Tabela1 z nizom
Tabela2 addwf PCL,f
DT "Padlo:" ;Tabela2 z nizom
Tabela4 addwf PCL,f
DT "Padlo skupaj:" ;Tabela4 z nizom
;****** Podprogrami za izpis niza iz tabele **********
LCD_Niz1
clrf Naslov ;Naslov = 0
bsf RS ;Podatkovni način
movlw .4
movwf Stevec2 ;Niz ima 4 znake (Met:)
Delaj1
movf Naslov,w
movwf Naslov
call Tabela1
call LCD_pisi ;Podatek -> LCD
incf Naslov,f ;Naslednji podatek
decfsz Stevec2,f ;Ali smo končali?
goto Delaj1 ;Ne še
bcf RS
return ;Da, izhod iz podprograma
LCD_Niz2
clrf Naslov ;Naslov = 0
bsf RS ;Podatkovni način
movlw .6
movwf Stevec1 ;Niz ima 6 znake (Padlo:)
Delaj2
movf Naslov,w
movwf Naslov
call Tabela2
call LCD_pisi ;Podatek -> LCD
incf Naslov,f ;Naslednji podatek
decfsz Stevec1,f ;Ali smo končali?
goto Delaj2 ;Ne še
bcf RS
return ;Da, izhod iz podprograma
LCD_Niz3
clrf Naslov ;Naslov = 0
bsf RS ;Podatkovni način
movlw .13
movwf Stevec ;Niz ima 13 znake (Padlo skupaj:)
Delaj3
movf Naslov,w
movwf Naslov
call Tabela4
call LCD_pisi ;Podatek -> LCD
incf Naslov,f ;Naslednji podatek
decfsz Stevec,f ;Ali smo končali?
goto Delaj3 ;Ne še
bcf RS ;Ukazni način
return ;Da, izhod iz podprograma
;************* Podprogram za izpis števila na LCD **************
Pisi_st
bsf Nicla,0
movlw .100
call Pretvori8 ;Pretvori stotice
movlw .10
call Pretvori8 ;Pretvori desetice
movf Podatek1,w ;Enice v delovni register
addlw '0' ;Pretvori v ASCII kodo
call LCD_pisi
return
;************* Podprogram za pretvorbo 8-bitnega števila v desetiško *********
Pretvori8 ;Pretvori 8-bitno število v desetiško
clrf Cifra ;Inicializacija
Nadaljuj
subwf Podatek1,f ;Vrednost registra W odšteje od vrednosti Podatek1, shrane nazaj v W
btfsc STATUS,C ;Ali je rezultat pozitiven? (glej podatke bitov v registru STATUS => bit C)
goto Ena_vec ;Da
addwf Podatek1,f ;Ne, naredi rezultat pozitiven
movf Cifra,w
addlw '0' ;Izračunaj ASCII kodo
btfsc Nicla,0 ;Je to vodilna ničla?
movlw ' ' ;Da, izpiši presledek LCD
call LCD_pisi ;Ne, izpiši cifro na LCD
return
Ena_vec
bcf Nicla,0
incf Cifra,f
goto Nadaljuj
Pisi_st1
bsf Nicla,0
movlw .10
call Pretvori8
movf Podatek1,w
addlw '0'
call LCD_pisi
return
end
Na kegljišču lahko pod vsaki kegelj namestimo fotoupor, zalit v prozorni araldit. Prostor nad keglji je osvetljen. Vsaki kegelj, ki ga podremo, povzroči osvetlitev fotoupora pod njim, zato se bo napetost na fotouporu spremenila. Ker uporabljamo digitalne vhodne pine mikrokontrolerja, mora biti sprememba napetosti takšna, da mikrokontroler to zazna, logična 0 => 0 V, logična 1 => 5 V. V ta namen lahko uporabimo integrirano vezje IC74132, ki vsebuje NIN (negirani IN) logična vrata s Schmittovim prožilnikom. Ko je fotoupor osvetljen, je na izhodu logičnih vrat napetost 5 V (logična 1), ko pa fotoupor zatemnimo, je na izhodu logičnih vrat napetost 0 V (logična 0). Izhodi NIN (NAND) logičnih vrat so povezani na vhodne priključke mikrokontrolerja PIC16f877a.
Enako lahko storimo za ugotavljanje števila metov, če za senzor uporabimo fotoupor, ki ga preko steze osvetljuje svetleča LED dioda ali laserska dioda. Vsaka krogla, ki jo odvržemo bo prekinila žarek do fotoupora.
Slika 4: Model kegljišča