Ś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.
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 Porembiński
; 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
A oto schemat ideowy urządzenia:
.