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:

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