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.

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.

schemat ideowy





Strona główna