Światła uliczne na PIC16F84

Program steruje modelem-zabawką świateł ulicznych. Zabawka była projektowana dla małych dzieci (poniżej 3 lat) i dużo czasu zajęło mi wymyślenie najlepszego sposobu włączania i wyłączania urządzenia.

Po długich przemyśleniach doszedłem do wniosku, że dziecko poradzi sobie z włączeniem zabawki ale raczej nie będzie pamiętało o jej wyłączeniu. Postanowiłem więc programowo usypiać mikrokontroler po odpowiednim czasie od momentu rozpoczęcia zabawy. Zabawka ma tylko jeden przycisk niestabilny (duży i odporny na działanie sił zewnętrznych), który posiada tylko jeden styk zwierny. Tym stykiem zwieramy wyprowadzenie MCLR mikrokontrolera do masy i powodujemy sprzętowy reset urządzenia. Po resecie program startuje i zaczyna się zabawa. Gdy minie ok. 8 minut (16 cykli zmiany świateł) w programie nastąpi przejście do rozkazu SLEEP i mikrokontroler wejdzie w stan uśpienia. W tym stanie pobór prądu ze źródła zasilania jest tak mały, że można o nim zapomnieć. W oryginale zastosowałem trzy akumulatorki NiCd połączone szeregowo i uznałem, że ich prąd samorozładowania jest porównywalny z prądem pobieranym przez uśpiony mikrokontroler. Zamiast akumulatorków polecam zastosować baterię płaską 3R12 o napięciu 4,5 V, która powinna wystarczyć na kilka miesięcy zabawy.

Diody LED są sterowane bezpośrednio z poszczególnych linii portu B. Jestem zwolennikiem sterowania urządzeniami zewnętrznymi za pomocą stanu niskiego na wyjściu portu. Stan nieaktywny reprezentowany jest przez wysoką impedancję linii portu. Taki sposób sterowania powoduje, że do zapalania i gaszenia diod wykorzystuję rejestr TRISB a nie PORTB. Zgaszona dioda podłączona do linii portu oznacza, że odpowiedni bit rejestru TRIS ustawiony jest w stan 1 (linia jest w stanie wysokiej impedancji). Wewnętrzne rezystory podciągające linie portu B nie są wykorzystywane.

Aby zapalić diodę ustawiamy odpowiednią linię portu w stan niski i pozwalamy, by prąd popłynął od plusa zasilania przez diodę i rezystor ograniczający prąd do portu mikrokontrolera. Linię przełączamy w stan wyjściowy wpisując 0 do odpowiedniego bitu rejestru TRISB i rejestru PORTB.

Do prawidłowego działania program musi w jakiś sposób odliczać opóźnienia czasowe. Można to zrobić na kilka sposobów. Najprostszy sposób to zastosowanie pustych pętli opóźniających ale ja wybrałem inną drogę. Wykorzystałem w tym celu sprzętowy licznik 8-bitowy i preskaler. Po podzieleniu zegara systemowego przez 256 w preskalerze i doprowadzeniu go do licznika uzyskujemy przerwanie po każdym przepełnieniu rejestru RTCC. W obsłudze przerwania ładujemy wartość początkową 12 do rejestru RTCC, więc przepełnienie następuje po zliczeniu 244 impulsów. Jak łatwo policzyć przerwanie będzie wywoływane co około 1/16 sekundy (dokładnie co 0.062464 sek.). Jeśli w obsłudze przerwania umieścimy rozkaz inkrementujący wartość jakiejś zmiennej (w tym przypadku jest to zmienna Licznik) to możemy łatwo sprawdzić ile czasu upłynęło od momentu wyzerowania tej zmiennej do dowolnej chwili z dokładnością do 1/16 sekundy.

Programy pisane w assemblerze mają jedną podstawową wadę: w tydzień po napisaniu nawet autor nie wie, jak działa program. Jedyną radą jest komentowanie prawie każdej instrukcji programu i dzielenie go na podprogramy oraz makroinstrukcje. Zastosowanie makr i podprogramów nie tylko zwiększa czytelność kodu programu ale także zmniejsza ilość błędów popełnianych przy pisaniu. Po zgromadzeniu biblioteki sprawdzonych makr możemy pisać programy w assemblerze tak łatwo jak w językach wysokiego poziomu.

Poniżej zamieszczam kod źródłowy programu i mam nadzieję, że komentarze są wystarczające dla zrozumienia jego działania. Oczywiście, posiłkowanie się notą katalogową jest niezbędne dla prawidłowego przeanalizowania kodu. No, chyba że ktoś zna to wszystko na pamięć.

Będąc użytkownikiem systemu Linux najczęściej piszę programy w edytorze vim lub Kate a kompiluję je przy pomocy kompilatora gpasm, który jest zgodny z firmowym kompilatorem MPASM z pakietu MPLAB.

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





;---------------------------------------------------------------------
; Program steruje modelem sygnalizacji swietlnej na skrzyzowaniu ulic.
; Jest to zabawka dla moich dzieci z okazji ukonczenia 2 i 3/4 roku zycia.
; Po 16 cyklach zmiany swiatel, co trwa ok. 8 minut, uklad przechodzi
; w stan uspienia.
; Mikrokontroler jednoukladowy PIC16F84 pracuje z kwarcem 4 MHz.
; Autor programu: Jacek Porembinski
; Pierwsza wersja z dnia 10 czerwca 2002 r.
; Data ostatniej modyfikacji: 15 sierpnia 2002 r.
;
;--------------------------------- Main declarations
	list P=PIC16f84
	include <p16f84.inc>

;--------------------------------- General Purpose Registers, user defined
w_copy		equ	0x0C	; kopia akumulatora
s_copy		equ	0x0D	; kopia rejestru STATUS

Licznik		equ	0x0E	; licznik czasu (wartosc biezaca)
Playtime	equ	0x0F	; licznik czasu trwania zabawy

;--------------------------------------------------------------------------
        __CONFIG   _CP_OFF & _WDT_OFF & _PWRTE_OFF & _XT_OSC

	org	0x000
	goto	Start

;--------------------------------- Subroutines

        org     0x004           ; Handler obslugi przerwania
                                ; zegarowego
        bcf     INTCON,02H      ; skasuj bit sygnalizujacy przerwanie
        movwf   w_copy          ; wykonaj kopie rejestru W
        movf    STATUS,W
        movwf   s_copy          ; wykonaj kopie rejestru statusu
        movlw   .12             ; zaladuj wartosc poczatkowa do RTCC taka,
        movwf   TMR0            ; by przerwanie wystepowalo co 1/16 sekundy.
        incf    Licznik,F       ; zwieksz licznik o 1
        movf    s_copy,W        ; zaladuj kopie STATUS'u do akumulatora
        movwf   STATUS          ; odtworz STATUS
        swapf   w_copy,F        ; a to sztuczka pozwalajaca zaladowac
        swapf   w_copy,W        ; do akumulatora jego kopie bez naruszenia
        retfie                  ; rejestru STATUS'u

;
;-------------------------------------------------------------------------
; Subroutine Delay_2 (opoznienie 2 sek.)                                 |
;-------------------------------------------------------------------------
;
Delay_2
	clrf    Licznik         ; Wyzeruj uplyw czasu
Del2	movf    Licznik,W	; zaladuj Licznik do W
	sublw   0x20		; odejmij 32
	btfss   STATUS,Z	; czy wynik rowny zero?
	goto    Del2		; nie, czekaj...
	return

;
;-------------------------------------------------------------------------
; Subroutine Delay_3 (opoznienie 3 sek.)                                 |
;-------------------------------------------------------------------------
;
Delay_3
	clrf    Licznik         ; Wyzeruj uplyw czasu
Del3	movf    Licznik,W	; zaladuj Licznik do W
	sublw   0x30		; odejmij 48
	btfss   STATUS,Z	; czy wynik rowny zero?
	goto    Del3		; nie, czekaj...
	return

;
;-------------------------------------------------------------------------
; Subroutine Delay_10 (opoznienie 10 sek.)                                 |
;-------------------------------------------------------------------------
;
Delay_10
		clrf    Licznik         ; Wyzeruj uplyw czasu
Del10           movf    Licznik,W	; zaladuj Licznik do W
                sublw   0xA0		; odejmij 160
                btfss   STATUS,Z	; czy wynik rowny zero?
                goto    Del10		; nie, czekaj...
                return

;***********************************************
;*        Glowna czesc programu                *
;***********************************************


Start
	bsf	STATUS,RP0	; Ustawienie strony 1
	movlw	b'11111111'	; Ustaw port B jako wyjscie
	movwf	TRISB ^ 0x080
	movlw	b'11111111'	; Ustaw port A jako wejscie
	movwf	TRISA ^ 0x080
	movlw	b'10000111'	; Prescaler do RTCC i dzieli /256
	movwf	OPTION_REG ^ 0x080
	bcf	STATUS,RP0	; Powrot do strony 0
	movlw	0x0A0		; Odblokowanie przerwan
	movwf	INTCON
	movlw	.16		; Ustaw czas zabawy na 16 cykli
	movwf	Playtime


Cykl

;  ---------------------------------------------------------------------
; |                     A       B       A       B                       |
; |     Czerwone        o       .      RB1     RB6                      |
; |     Zolte           .       .      RB2     RB5                      |
; |     Zielone         .       o      RB3     RB4                      |
; |                                                                     |
;  ---------------------------------------------------------------------
	movlw	b'11101101'	; Ustaw RB1 i RB4 rowne 0
	movwf	PORTB		; i wpisz do portu B
	bsf	STATUS,RP0	; Ustawienie strony 1
	movlw	b'11101101'	; Ustaw RB1 i RB4 jako wyjscie
	movwf	TRISB ^ 0x080	;
	bcf	STATUS,RP0	; Wroc na strone 0
	call	Delay_10	; Odczekaj 10 sekund

;  ---------------------------------------------------------------------
; |                     A       B       A       B                       |
; |     Czerwone        o       .      RB1     RB6                      |
; |     Zolte           .       o      RB2     RB5                      |
; |     Zielone         .       .      RB3     RB4                      |
; |                                                                     |
;  ---------------------------------------------------------------------
	movlw	b'11011101'	; Ustaw RB1 i RB5 rowne 0
	movwf	PORTB		; i wpisz do portu B
	bsf	STATUS,RP0	; Ustawienie strony 1
	movlw	b'11011101'	; Ustaw RB1 i RB5 jako wyjscie
	movwf	TRISB ^ 0x080	;
	bcf	STATUS,RP0	; Wroc na strone 0
	clrf	Licznik		; Wyzeruj uplyw czasu
	call	Delay_3		; Odczekaj 3 sekundy

;  ---------------------------------------------------------------------
; |                     A       B       A       B                       |
; |     Czerwone        o       o      RB1     RB6                      |
; |     Zolte           o       .      RB2     RB5                      |
; |     Zielone         .       .      RB3     RB4                      |
; |                                                                     |
;  ---------------------------------------------------------------------
	movlw	b'10111001'	; Ustaw RB1, RB2 i RB6 rowne 0
	movwf	PORTB		; i wpisz do portu B
	bsf	STATUS,RP0	; Ustawienie strony 1
	movlw	b'10111001'	; Ustaw RB1, RB2 i RB6 jako wyjscie
	movwf	TRISB ^ 0x080	;
	bcf	STATUS,RP0	; Wroc na strone 0
	clrf	Licznik		; Wyzeruj uplyw czasu
	call	Delay_2		; Odczekaj 2 sekundy

;  ---------------------------------------------------------------------
; |                     A       B       A       B                       |
; |     Czerwone        .       o      RB1     RB6                      |
; |     Zolte           .       .      RB2     RB5                      |
; |     Zielone         o       .      RB3     RB4                      |
; |                                                                     |
;  ---------------------------------------------------------------------
	movlw	b'10110111'	; Ustaw RB3 i RB6 rowne 0
	movwf	PORTB		; i wpisz do portu B
	bsf	STATUS,RP0	; Ustawienie strony 1
	movlw	b'10110111'	; Ustaw RB3 i RB6 jako wyjscie
	movwf	TRISB ^ 0x080	;
	bcf	STATUS,RP0	; Wroc na strone 0
	clrf	Licznik		; Wyzeruj uplyw czasu
	call	Delay_10	; Odczekaj 10 sekund

;  ---------------------------------------------------------------------
; |                     A       B       A       B                       |
; |     Czerwone        .       o      RB1     RB6                      |
; |     Zolte           o       .      RB2     RB5                      |
; |     Zielone         .       .      RB3     RB4                      |
; |                                                                     |
;  ---------------------------------------------------------------------
	movlw	b'10111011'	; Ustaw RB2 i RB6 rowne 0
	movwf	PORTB		; i wpisz do portu B
	bsf	STATUS,RP0	; Ustawienie strony 1
	movlw	b'10111011'	; Ustaw RB2 i RB6 jako wyjscie
	movwf	TRISB ^ 0x080	;
	bcf	STATUS,RP0	; Wroc na strone 0
	clrf	Licznik		; Wyzeruj uplyw czasu
	call	Delay_3		; Odczekaj 3 sekundy

;  ---------------------------------------------------------------------
; |                     A       B       A       B                       |
; |     Czerwone        o       o      RB1     RB6                      |
; |     Zolte           .       o      RB2     RB5                      |
; |     Zielone         .       .      RB3     RB4                      |
; |                                                                     |
;  ---------------------------------------------------------------------
	movlw	b'10011101'	; Ustaw RB1, RB5 i RB6 rowne 0
	movwf	PORTB		; i wpisz do portu B
	bsf	STATUS,RP0	; Ustawienie strony 1
	movlw	b'10011101'	; Ustaw RB1, RB5 i RB6 jako wyjscie
	movwf	TRISB ^ 0x080	;
	bcf	STATUS,RP0	; Wroc na strone 0
	clrf	Licznik		; Wyzeruj uplyw czasu
	call	Delay_2		; Odczekaj 2 sekundy

	decfsz	Playtime,F	; Czy zakonczyc zabawe?
	goto	Cykl

	movlw	b'11111111'	; Ustaw wszystkie linie portu B na 1
	movwf	PORTB
	bsf	STATUS,RP0	; Ustaw strone 1
	movlw	b'11111111'	; Ustaw caly port B jako wejscie
	movwf	TRISB ^ 0x080	; Wpisz powyzsze ustawienia portu B.
	bcf	STATUS,RP0	; Wroc na strone 0

	movlw	b'11111111'	; Ustaw wszystkie linie portu A na 1
	movwf	PORTA
	bsf	STATUS,RP0	; Ustaw strone 1
	movlw	b'11111111'	; Ustaw caly port A jako wejscie
	movwf	TRISA ^ 0x080	; Wpisz powyzsze ustawienia portu A.
	bcf	STATUS,RP0	; Wroc na strone 0
	sleep			; Dobranoc!

	end




Poniżej zamieszczam schemat ideowy urządzenia narysowany w prostym programie graficznym KolourPaint.

schemat ideowy





Strona główna