Niezapominajki

Geek clock na PIC16F628A


Zegarek z wyświetlaniem czasu w systemie binarnym powstał bez jakiejś wyraźnej potrzeby. Chciałem zbudować coś dziwacznego i zupełnie nieprzydatnego a przy okazji nauczyć się czegoś nowego o mikrokontrolerach z rodziny PIC16.

Pierwszy projekt powstał na mikrokontrolerze PIC16F877, ponieważ dysponuje on dużą ilością portów i nie trzeba się specjalnie wysilać z obsługą wyświetlacza. Stwierdziłem jednak, że w ten sposób przetrenuję tylko wykorzystanie układu TIMER1 z jego układem oscylatora dla kwarcu zegarkowego. No i układ jest trochę duży jak na tak proste zadanie.

Postanowiłem utrudnić sobie życie i zastosować mikrokontroler PIC16F628 (z mocno ograniczoną w stosunku do porzednika liczbą pinów) oraz, w roli wyświetlacza, matrycę LED 5x7 punktów. Od razu stało się jasne, że trzeba będzie zastosować sterowanie diodami z multipleksowaniem. W tym momencie pomyślałem o matrycy ze wspólną anodą w kolumnach i tranzystorami p-n-p do zasilania kolumn diod. Niestety, w jednym sklepie nie było wyświetlaczy 5x7 a w innym był tylko jeden typ: LTP-757Y. Nie pozostało mi nic innego, jak dostosować się do tego, co było dostępne.

Kupując wyświetlacz dowiedziałem się od sprzedawcy tylko tyle, że ma on żółty kolor świecenia. Dopiero w domu ściągnąłem sobie z sieci datasheet tej kostki i zorientowałem się, że w kolumnach ma on katody a w wierszach anody. Wymusza to zastosowanie tranzystorów n-p-n do zwierania katod do masy. Zasilanie (plus) podajemy na wiersze (anody diod LED) z linii portu mikrokontrolera przez rezystory szeregowe ograniczające prąd. W wyświetlaczu nie wykorzystuje się najwyższego wiersza, ponieważ do wyświetlenia liczb z zakresu 0-59 wystarcza 6 diod LED. Liczby binarne reprezentujące godziny, minuty i sekundy są wyświetlane w pionie. Uznałem, że tak będzie czytelniej.

Program korzysta z dwóch przerwań od wewnętrznych liczników/timer'ow. Odliczaniem czasu zajmuje się TIMER1, którego 16-bitowy licznik zlicza impulsy z oscylatora pracującego z zewnętrznym kwarcem zegarkowym 32768 Hz. Przy każdym przepełnieniu licznika procedura obsługi przerwania ustawia najstarszy bit rejestru TMR1H, co jest równoznaczne z zainicjowaniem licznika wartością początkową równą 32768. Dzięki temu przerwanie występuje dokładnie co sekundę. W programie obsługi przerwania zrealizowana jest właściwie cała procedura liczenia czasu - sekund, minut i godzin.
Przerwanie od TIMER0 zajmuje się wyświetlaniem czasu na wyświetlaczu LED 5x7. Wyświetlacz jest przemiatany ponad 80 razy na sekundę co daje wrażenie, że świeci się stale. Jednocześnie wpatrywanie się w świecące diody LED nie męczy wzroku.

Po włączeniu zasilania wszystkie diody są zgaszone (godzina 0:00:00) i należy zegarek ustawić. Wciskanie przycisku HOUR powoduje zwiększanie liczby godzin a wciskanie przycisku MIN skutkuje zwiększaniem liczby minut. Po skończeniu ustawiania godzin i minut należy wcisnąć przycisk OK, co spowoduje wystartowanie zliczania upływu czasu. Sekundy zawsze startują od zera i nie ustawia się ich.




A oto treść programu w języku assemblera PIC16F628:



;---------------------------------------------------------------
; Mikrokontroler jednoukladowy PIC16F628A pracuje z wewnetrznym
; oscylatorem RC i kwarcem zegarkowym na RB6 - RB7
;
; Autor programu: Jacek Porembiński
; Data ostatniej modyfikacji: 12 stycznia 2008 r.
;----------------------------------------------------------------

;--------------------------------- Main declarations
    LIST    P=PIC16F628A
    include <p16f628a.inc>
;
    ERRORLEVEL -302
;--------------------------------- Hardware declaration
    __CONFIG   h'3F78'

    CBLOCK  0x020
    S_TEMP  ; Kopia rejestru STATUS-u
    Sekundy ; licznik sekund
    Minuty  ; licznik minut
    Godziny ; licznik godzin
    Temp    ; zmienna pomocnica
    Col     ; kolumna wyswietlacza
    ENDC

W_TEMP  EQU 0x070   ; Kopia akumulatora

Min     EQU 3       ; ustawianie minut RA3
Hour    EQU 4       ; ustawianie godzin RA4
OK      EQU 7       ; OK pin RA7

SET_BANK_0:MACRO
    bcf STATUS, RP0
    bcf STATUS, RP1
    ENDM

SET_BANK_1:MACRO
    bsf STATUS, RP0
    bcf STATUS, RP1
    ENDM

;----- Main program -----

    org     0x000

    goto    Main_Start

;=========================================================================
; Podprogramy moga byc umieszczone w adresach ponizej 0x0FF
;=========================================================================

    org     0x004       ; od tego adresu musi sie zaczynac procedura
                        ; obslugi przerwania zegarowego


;
;********************************
;* Obsluga przerwan od timer'ow *
;********************************
;
    movwf   W_TEMP
    swapf   STATUS,W
    bcf     STATUS,RP0
    bcf     STATUS,RP1
    movwf   S_TEMP
    btfsc   PIR1,TMR1IF
    goto    Timer1
;
;******************************
;* Obsluga przerwania TIMER0 *
;*****************************
;
Timer0
    incf    Col,F       ; zwieksz Col
    movf    Col,W       ; Col do akumulatora
    xorlw   0x03        ; i sprawdzamy, czy osiagnelo 3
    btfsc   STATUS,Z    ; przeskocz jesli NIE
    clrf    Col         ; TAK: wyzeruj Col


    movf    Col,F       ; zaladuj Col do Col
    btfsc   STATUS,Z    ; przeskocz jesli nie zero
    goto    Col_1       ; idz do "kolumna pierwsza"
    btfsc   Col,1       ; przeskocz jesli Col = 1 ?
    goto    Col_2       ; idz do "kolumna druga"

Col_3
    clrw
    movwf   PORTB       ; zgas wszystkie diody
    movlw   b'11111001'
    movwf   PORTA       ; uaktywnij trzecia kolumne
    movf    Sekundy,W   ; wczytaj sekundy do W
    movwf   PORTB       ; i wyrzuc na PORTB
    goto    KonT0Int

Col_2
    clrw
    movwf   PORTB       ; zgas wszystkie diody
    movlw   b'11111010'
    movwf   PORTA       ; uaktywnij druga kolumne
    movf    Minuty,W    ; wczytaj minuty do W
    movwf   PORTB       ; i wyrzuc na PORTB
    goto    KonT0Int

Col_1
    clrw
    movwf   PORTB       ; zgas wszystkie diody
    movlw   b'11111100'
    movwf   PORTA       ; uaktywnij pierwsza kolumne
    movf    Godziny,W   ; wczytaj godziny do W
    movwf   PORTB       ; i wyrzuc na PORTB

KonT0Int
    bcf     INTCON,T0IF ; skasuj bit sygnalizujacy przerwanie
    goto    EndOfInt
;
;******************************************
;* Obsluga przerwania TIMER1 co 1 sekunde *
;******************************************
;
Timer1
    bsf     TMR1H,7     ; ustaw najstarszy bit licznika Timer1
    incf    Sekundy,F   ; zwieksz licznik sekund o 1
    movf    Sekundy,W   ; zaladuj sekundy do akumulatora
    xorlw   .60         ; porownaj z 60
    btfss   STATUS,Z    ; czy sekundy = 60 ???
    goto    wyjscie     ; NIE: wyjdz z przerwania
    clrf    Sekundy     ; TAK: wyzeruj sekundy
    incf    Minuty,F    ; zwieksz licznik minut
    movf    Minuty,W    ; zaladuj minuty do akumulatora
    xorlw   .60         ; porownaj z 60
    btfss   STATUS,Z    ; czy minuty = 60 ???
    goto    wyjscie     ; NIE: wyjdz z przerwania
    clrf    Minuty      ; TAK: wyzeruj minuty
    incf    Godziny,F   ; zwieksz licznik godzin
    movf    Godziny,W   ; zaladuj godziny do akumulatora
    xorlw   .24         ; porownaj z 24
    btfsc   STATUS,Z    ; czy godziny = 24 ???
    clrf    Godziny     ; TAK: wyzeruj godziny
                        ; NIE: wyjdz z przerwania
wyjscie
    bcf     PIR1,TMR1IF ; skasuj bit sygnalizujacy przerwanie
EndOfInt
    swapf   S_TEMP,W
    movwf   STATUS
    swapf   W_TEMP,F
    swapf   W_TEMP,W
    retfie


Delay
    movlw   0xD0        ; petla zewnetrzna 32 razy
    clrf    Temp        ; wyzeruj Temp
lab decfsz  Temp,F      ; Temp-- i przeskocz gdy Temp==zero
    goto    lab         ; powtarzaj 256 razy
    addlw   0x01        ; W++
    btfss   STATUS,Z    ; przeskocz gdy W==0
    goto    lab
    return              ; Temp == 0 wiec wroc z podprogramu



;-----------------------
; Glowna czesc programu
;-----------------------

Main_Start
    SET_BANK_0          ; Select Bank 0
    clrf    TMR1L
    clrf    TMR1H
    clrf    Sekundy
    clrf    Minuty
    clrf    Godziny
    movlw   0x07
    movwf   CMCON
    clrf    PORTB
    movlw   0xFF
    movwf   PORTA
    SET_BANK_1          ; Select Bank 1
    movlw   b'11111000' ; piny RA0, RA1 i RA2
    movwf   TRISA       ; ustaw jako wyjscia
    movlw   b'11000000' ; piny RB7 i RB6 jako wejscia
    movwf   TRISB       ; pozostale pracuja jako wyjscia
    movlw   b'10000011' ; prescaler 1:16
    movwf   OPTION_REG
    SET_BANK_0          ; Select Bank 0
    clrf    TMR0
    bcf INTCON,T0IF     ; skasuj flage przerwania
    bsf INTCON,T0IE     ; wlacz przerwanie od Timer0
    bsf INTCON,GIE      ; oraz globalnie

;---------------------------------------------------------------
Ustaw
    SET_BANK_0
    btfss   PORTA,OK    ; przeskocz gdy nie wcisniety OK
    goto    SprOK       ; sprawdz, czy koniec ustawiania
    btfss   PORTA,Min   ; przeskocz, gdy nie wcisniety Min
    goto    SprMin      ; sprawdz, czy ust. minut
    btfss   PORTA,Hour  ; przeskocz, gdy nie wcisniety Hour
    goto    SprHour     ; sprawdz, czy ust. godzin
    goto    Ustaw       ; czekaj na wcisniecie klawisza

SprOK
    call    Delay       ; odczekaj chwile (drgania stykow)
    btfss   PORTA,OK    ; przeskocz, gdy nie wcisniety OK
    goto    Zegar       ; wystartuj zegar
    goto    Ustaw       ; wroc do ustawiania zegara

SprMin
    call    Delay       ; odczekaj chwile
    btfsc   PORTA,Min   ; przeskocz, gdy wcisniety Min
    goto    Ustaw       ; wroc na poczatek ustawiania
    incf    Minuty,F    ; zwieksz minuty
    movf    Minuty,W    ; sprawdz, czy nie przekroczono 60
    xorlw   .60         ;
    btfsc   STATUS,Z    ; przeskocz, gdy (Minuty≠60)
    clrf    Minuty      ; wyzeruj minuty
L1  btfss   PORTA,Min   ; czy puszczono przycisk Min?
    goto    L1          ; jesli nie, to czekaj
    call    Delay       ; odczekaj chwile (debounce)
    btfss   PORTA,Min   ; czy naprawde puszczony klawisz?
    goto    L1          ; nie, czekaj dalej na zwolnienie przycisku
    goto    Ustaw       ; przycisk zwolniony, wroc do ustawiania

SprHour
    call    Delay       ; odczekaj chwile
    btfsc   PORTA,Hour  ; przeskocz, gdy wcisniety Hour
    goto    Ustaw       ; wroc na poczatek ustawiania
    incf    Godziny,F   ; zwieksz godziny
    movf    Godziny,W   ; sprawdz, czy nie przekroczono 24
    xorlw   .24     ;
    btfsc   STATUS,Z    ; przeskocz, gdy (Godziny≠24)
    clrf    Godziny     ; wyzeruj godziny
L2  btfss   PORTA,Hour  ; czy puszczono przycisk Hour?
    goto    L2          ; jesli nie, to czekaj
    call    Delay       ; odczekaj chwile (debounce)
    btfss   PORTA,Hour  ; czy naprawde puszczony klawisz?
    goto    L2          ; nie, czekaj dalej na zwolnienie przycisku
    goto    Ustaw       ; przycisk zwolniony, wroc do ustawiania


;---------------------------------------------------------------
Zegar
    movlw   b'00001011' ;
    movwf   T1CON       ; oscylator 32768 Hz na RB6 i RB7, preskaler 1:1
    bsf INTCON,PEIE     ; zezwol na przerwania od 2^15
    bsf PIE1,TMR1IE     ; odblokuj odliczanie czasu
    goto    $           ; petla bez konca


    end

            

A oto schemat ideowy urządzenia:

.

schemat ideowy






Strona główna