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.
Program napisałem w edytorze Kate a skompilowałem kompilatorem gpasm ale sprawdziłem również poprawność kompilacji w środowisku MPLAB pracującym na komputerach z systemem operacyjnym Windows 98 i Windows XP.
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 Porembinski ; 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
Poniżej zamieszczam schemat ideowy urządzenia narysowany w prostym programie graficznym KolourPaint.