
Witajcie moi drodzy
Oto druga część mojego praktycznego tutoriala dla ośmiobitowego mikrokontrolera PIC18F2550 od Microchipa i programatora SDCC. W tym temacie omówię podstawowe operacje cyfrowego IO na prostych przykładach.
Spis części (osobnych tematów) tutoriala
Tutorial podzielony jest na osobne tematy i tutaj znajdują się do nich linki.
Część 1 - Konfiguracja środowiska pracy
https://www.elektroda.pl/rtvforum/viewtopic.php?p=18304424#18304424
Część 2 - Blink LED, piny IO, cyfrowe wejścia i wyjścia
https://www.elektroda.pl/rtvforum/viewtopic.php?p=18389188#18389188
Część 3 - Ustawienia oscylatora. Oscylator wewnętrzny, zewnętrzny, rezonator kwarcowy, PLL
https://www.elektroda.pl/rtvforum/topic3657704.html
Część 4 - Timery, przerwania
https://www.elektroda.pl/rtvforum/viewtopic.php?p=18580858#18580858
Część 5 - Obsługa wyświetlacza siedmiosegmentowego
https://www.elektroda.pl/rtvforum/viewtopic.php?p=18580877#18580877
Spis treści będzie uzupełniany wraz z pisaniem przeze mnie kolejnych części.
Ten temat to jest część 2, czyli Blink LED, piny IO, cyfrowe wejścia i wyjścia. Zaczynamy.
Wstęp
W tej części tutoriala pokażę najprostsze operacje na portach cyfrowego IO mikrokontrolera PIC18F2550 przy użyciu kompilatora SDCC. Dokładnie przeanalizujemy kod migania diodą, tzw. ''''blink LED'''' czyli ''''hello world'''' mikrokontrolerów, posterujemy większą ilością LEDów na różne sposoby, a potem wprowadzimy przycisk, do którego użyjemy raz zewnętrznych, a raz wewnętrznych rezystorów pull-up.
Wszystko będzie oparte o osobne, praktyczne przykłady z kodem dostępnym do pobrania i prezentacją działania.
Całość zrealizujemy na płytce stykowej, używając wewnętrznego oscylatora 1MHz.
Jak programować PIC18F2550?
W ramach odpowiedzi na te pytanie odsyłam do pierwszej części mojego tutoriala:
https://www.elektroda.pl/rtvforum/topic3635522.html
Tam jest wszystko opisane (akapit: Zaczynamy działanie z PIC18F2550).
Uwaga - język C
W tej części zakładam, że czytelnik ma jakąś podstawową znajomość języka C (albo chociaż jakiegokolwiek podobnego języka programowania). Bez tego może być ciężko a tłumaczenie tu wszystkiego od 0 sprawiłoby, że cały temat naprawdę byłby wielki i też pewnie dla niektórych nudny. Znajomość podstawowej struktury kodu i operatorów z tego języka naprawdę się tu przyda.
Przyda się też znajomość systemu binarnego i szesnastkowego zapisu liczb.
Teoria - porty PIC18F2550
Porty PIC18F2550 są ośmiobitowe (tak jak i sam mikrokontroler), czyli każdy port (A, B, itp.) ma co najwyżej 8 pinów. Każdy bit określa stan na danym pinie (niski lub wysoki). Poszczególne piny każdego portu mogą być indywidualnie ustawione w tryb wejścia (input) lub wyjścia (output).
Tryb pinu ustawia z pomocą rejestru TRIS* (dla portu A jest to TRISA, dla portu B TRISB, itp). Zapalony bit (1) oznacza tryb wejścia, zgaszony bit (0) oznacza tryb wyjścia.
Do odczytu wartości z portu służy rejestr PORT* (dla portu A to jest PORTA, dla portu B PORTB, itp).
Do zapisu wartości służy rejestr LAT* (dla portu A to jest LATA, dla portu B LATB, itp).
Podsumowując, dla każdego portu mamy trzy rejestry:
Rodzaj rejestru | Przykłady | Zastosowanie |
TRIS* | TRISA, TRISB, TRISC | Określa tryb pinów portu (czy dany pin portu jest wejściem czy wyjściem) |
PORT* | PORTA, PORTB, PORTC | Pozwala odczytać bieżącą, rzeczywistą zawartość portu |
LAT* | LATA, LATB, LATC | Do tego portu zapisujemy wartość, na jaką chcemy ustawić port |
Czemu jest osobno PORT* i LAT*? Różnica jest w odczycie. Odczytywanie wartości LAT* zwraca to, co do niej wpisaliśmy. Natomiast odczytywanie PORT* zwraca rzeczywistą, bieżącą wartość pinu. W niektórych sytuacjach to mogą być różne wartości, gdyż zmiana stanu na pinie jednak trochę trwa.
Porty często są współdzielone z innymi funkcjami i nie działają, gdy te funkcjonalności są włączone (przykładowo porty wejścia przetwornika ADC).
Funkcje specjalne - takie jak komunikacja UART, USB, SPI, I2C są z reguły tylko na wybranych pinach które też mogą mieć rolę zwykłego IO, lecz nimi się tutaj nie zajmujemy.
Należy pamiętać o tym, że też są inne rejestry które wpływają na działanie pinów PICa - tutaj chociażby ADCON1, CMCON itp. które są odpowiedzialne za konfigurację trybu ADC i komparatora.
Przyjrzyjmy się temu jakie porty oferuje PIC18F2550:

Mamy tutaj port A, port B, port C oraz jeden pin portu E (jeśli wyłączymy funkcję MCLR, w przeciwnym razie ten pin ma rolę RESET).
Nie wszystkie bity portów są wyprowadzone, np. RC3 w ogóle nie ma (na starszych PICach był, ale przejął tu rolę VUSB).
Niektóre piny mają ograniczoną funkcjonalność. Mogą być np. tylko w trybie wejścia. Są to na przykład RC4 i RC5:

Proszę zwrócić uwagę, na powyższym zrzucie ekranu z noty katalogowej widać jasno, że np. RC2 to jest "Digital I/O", ale już RC4 to tylko "Digital input".
Wynika to stąd, że RC4 i RC5 mogą mieć też funkcję D+/D- (linie sygnałowe od USB):

Piny współdzielące tryb IO (input/output cyfrowy) z wejściami analogowymi (ADC - Analog to Digital Converter) mogą też wymagać do użycia poprawnego ich skonfigurowania w rejestrze ADCON1.
Jeśli np. chcemy użyć RB0 jako wejścia, to powinniśmy mu wyłączyć tryb analogowy, gdyż ma on też funkcję AN12.
Zawartość ADCON1 przedstawia obrazek:

Najbardziej interesującą nas tu konfigurację, czyli wybór pomiędzy trybem IO pina a trybem analogowym przedstawia tabelka:

Jak widać, bity PCFG3:PCFG0 określają tryb wejść AN0-AN12. Nie jest możliwa dowolna konfiguracja trybów tych pinów, można tylko ustawić te co są wspierane przez PICa.
Wbrew pozorom to nie jest nic trudnego - nawet w samej nocie katalogowej są przykłady jak wyłączyć tryb analogowy dla danego portu:

Co prawda jest to assembler, ale komentarze też wyjaśniają co się dzieje.
Pokrewny PIC18F4550 ma nieco więcej portów:

Migracja z PIC18F2550 do PIC18F4550 jest bardzo łatwa i jeśli braknie nam wolnych portów to zapraszam do przeprowadzki.
Porty PIC18F2550 są w stanie znieść dość duże prądy, ale mimo to należy pamiętać o ich ograniczeniach. Nie można z portu pociągnąć zbyt dużego prądu. Jeden pin jest w stanie znieść do 25 mA, natomiast wszystkie w sumie wszystkie porty nie powinny nieść więcej niż 200 mA. Wszystkie parametry znajdują się w nocie katalogowej:

Projektując nasz układ musimy o tym pamiętać. Do sterowania czymkolwiek chociaż troszkę prądożernym musimy stosować odpowiednie układy. Nawet większą ilość diod LED (czy tam wyświetlacz) powinniśmy zasilać przez tranzystor, a pinem IO tylko go przełączać.
Dla zainteresowanych; wewnętrzna struktura portu IO PICa wygląda tak:

Piny IO mają diody ochronne do VDD i GND.
O wiele więcej i bardziej szczegółowo o pinach PIC18F2550 można poczytać w jego nocie katalogowej - i to do niej zainteresowanych odsyłam. Sam tutaj skupię się już przede wszystkim na praktycznych przykładach użycia.
Pin MCLR/VPP czyli RESET
Jednym ze specjalnych pinów PICa który można tu przy okazji omówić jest pin MCLR/VPP, czyli tzw. RESET.

Większość czytelników zapewne kojarzy pin RESET z Arduino lub z AVRów - ten RESET ma identyczną funkcję.
Nazwa MCLR pochodzi od Master Clean.
Stan wysoki na tym pinie umożliwia działanie mikrokontrolera, natomiast stan niski go resetuje. Stąd właśnie ten pin zazwyczaj podłączony jest przez opornik 10k do zasilania (to wymusza domyślnie stan wysoki), i do masy przez przycisk (to pozwala nam poprzez wciśnięcie przycisku ustawić tam stan niski i zresetować PICa).
Pin też również bierze udział w programowaniu PICa (razem z PGD/PGC).
Schemat pokazuje typowe połączenie pinu MCLR w większości konstrukcji:

Możliwe jest jednak wyłączenie funkcji MCLR tego pinu i użycie go jako jedno z wejść portu E poprzez zmianę bitu konfiguracji MCLRE (MCLR Enabled):

Niestety nawet wtedy ten pin nie może mieć funkcji wyjścia, a programator i tak dalej do niego się podłącza, więc my nie będziemy używać go w roli PORTE.
Analizujemy kod ''''blink LED''''
Na początek przeanalizujemy kod ''''blink LED'''' (miganie diodą LED) z poprzedniej części.
Uruchomiliśmy go na PIC18F2550 na płytce stykowej w takim układzie:


Schemat połączeń ze zdjęcia powyżej:

(VDD i GND oczywiście zarówno podłączone jest do zasilania z USB jak i do ICSP z PICKIT3, co widać też na zdjeciu wcześniej).
Dla przypomnienia, tu jest całość kodu:
Code: c
Całość w formie załącznika (też ze skompilowanym wsadem):
Kod ten ustawia pin RC0 w trybie wyjścia a potem przełącza jego stan co pół sekundy:

Używa wewnętrznego oscylatora PICa.
Teraz dokładnie go przeanalizujemy.
Code: c
W powyższym fragmencie załączamy nagłówki funkcji z których chcemy korzystać, tak jak normalnie w języku C.
Nagłówek pic18fregs.h pochodzi z SDCC i mówi kompilatorowi o tym, jakie mamy dostępne rejestry dla naszego PICa (np. TRISB, LATB, ADCON1, itp).
Nagłówek delay.h pochodzi z SDCC i zawiera funkcje oczekiwania daną ilość cykli (delay1ktcy, delay10ktcy, itp)
Tutaj też możemy umieszczać własne nagłówki.
Code: c
W powyżej zacytowanym fragmencie znajduje się ustawienie bitów konfiguracyjnych PICa z pomocą dyrektyw preprocesora języka C. Ich dokładny opis można znaleźć w datasheet PIC18F2550 oraz w dokumentacji SDCC.
W tym przypadku ustawiamy:
- XINST na OFF - wyłączamy extended instructions set. SDCC w bieżącej wersji nie wspiera rozszerzonego zestawu instrukcji.
- FOSC na INTOSCIO_EC - ustawiamy oscylator na tryb wewnętrznego oscylatora, dzięki czemu PIC nie wymaga oscylatora kwarcowego by ruszyć (w następnej części tutorialu dokładnie omówię to ustawienie)
- WDT na OFF - wyłączamy watchdog timer. Watchdog timer pozwala automatycznie zresetować mikroprocesor gdy się ten zawiesi. Na ten moment nie jest nam to potrzebne, ale możliwe, że poznamy go szczegółowo w dalszych częściach.
- LVP na OFF - wyłączamy Low Voltage Programming. Pozwala on wgrywać wsad na PIC bez podania VPP (jakieś 12V) na pin RESET. Dla nas zbędne - zapewne korzystamy z PICKIT2 lub PICKIT3. Co do innych programatorów to się nie wypowiem, gdyż po prostu nie znam wszystkich dostępnych.
Code: c
Powyżej jest główna funkcja wsadu, słynne "main" która sama się wykonuje od nowa po wyjściu PICa ze stanu RESET.
Code: c
Powyższy fragment ustawia najmłodszy bit (bit 0) z portu C (pin RC0) na tryb wyjścia z pomocą struktury TRISCbits. Równie dobrze można to zrobić z pomocą rejestru TRISC (o tym nieco dalej).
Code: c
Powyżej pokazana jest główna pętla (main loop) programu, nieskończona. W pętli negowana jest wartość portu RC0 oraz użyta jest funkcja oczekiwania delay1ktcy(125) z nagłówka delay.h (biblioteka libc18f.lib). Dzięki temu uzyskujemy miganie diody LED co pół sekundy.
Jak działa delay z delay.h?
Funkcja delay po prostu odczekuje daną ilość cyklów instrukcji PICa.
Tutaj trzeba zaznaczyć, że zegar instrukcji PICa działa z 1/4 częstotliwości jego oscylatora.
Czyli to, ile czasu przeczeka np. wywołanie delay1ktcy(125) zależy od tego, jak mamy skonfigurowany oscylator PICa.
W przypadku użycia domyślnego, wewnętrznego oscylatora 1MHz (tak jak w kodach w tym temacie) mamy:
Fosc (częstotliwość oscylatora) = 1MHz
Finst (częstotliwość instrukcji) = 0.25MHz
Tinst (czas instrukcji) = 0.004ms
Zatem ile instrukcji musimy odczekać by minęło 500ms?
500ms / 0.004ms = 125 000 instrukcji.
Stąd w naszym kodzie jest:
Code: c
Czemu ''''125'''' a nie ''''125 000''''? Funkcja to jest delay1ktcy, w nazwie jest ''''1k'''', czyli jeden tysiąc instrukcji razy 125.
W delay.h są funkcje:
- delay10tcy ( unsigned char n ) - odczekuje n razy 10 cykli instrukcji
- delay100tcy ( unsigned char n ) - odczekuje n razy 100 cykli instrukcji
- delay1ktcy ( unsigned char n ) - odczekuje n razy tysiąc cykli instrukcji
- delay10ktcy ( unsigned char n ) - odczekuje n razy 10 tysięcy cykli instrukcji
- delay100ktcy ( unsigned char n ) - odczekuje n razy 100 tysięcy cykli instrukcji
- delay1mtcy ( unsigned char n ) - odczekuje n razy milion cykli instrukcji
UWAGA: Parametrem tej funkcji jest typ unsigned char, ośmiobitowy, więc nie możemy sobie napisać ''''delay10tcy(12500)'''' i liczyć, że zadziała. Znajomość typów z języka C, ich rozmiarów i limitacji jest tutaj bardzo wskazana!
Same funkcje delay* od strony kompilatora zaimplementowane są już w assemblerze i używają instrukcji nop ("no operation - brak działania") do odczekania danej ilości cykli. Przykładowa implementacja z delay1ktcy.S:
Code: asm
Na szczęście to już jest w naszych bibliotekach kompilatora i nie musimy tego sami pisać.
Miganie diodą LED - różne sposoby
Do rejestrów związanych z portami (TRISC, LATC, itp) można dostać się też jak do normalnej zmiennej, bez użycia struktur "bits" (TRISCbits oraz LATCbits; analogicznie dla pozostałych rejestrów).
Te struktury są tylko ułatwieniem dla użytkownika i nie trzeba z nich korzystać.
Jeśli nie chcemy z nich korzystać, to możemy operować na LATC i TRISC (oraz analogicznie na rejestrach innych portów) tak jak na zmiennych typu integer (liczba całkowita).
Z punktu widzenia kompilatora takie TRISCbits to jest to samo co TRISC, co zresztą można podejrzeć w jego nagłówkach, np w include/pic16/pic18f2550.h:
Code: c
Powyższy kod znajduje się w nagłówku dla PIC18F2550 którego powinniśmy mieć już zainstalowanego razem z kompilatorem. Widzimy tu, że TRISCbits jest strukturą (struct) pozwalającą na wygodny dostęp do poszczególnych bitów, a samo TRISC jest typu __sfr, czyli Special Function Register. Oczywiście nie musimy tego kodu sami pisać. Pokazuję go tylko w ramach ciekawostki. Zapis __at(0x0F94) mówi kompilatorowi w którym miejscu pamięci wybrany PIC ma właśnie rejestr odpowiedzialny za TRISC. Wartość ta bierze się z jego noty katalogowej, z sekcji SPECIAL FUNCTION REGISTERS, a dokładniej z tej tabelki:

Poniżej kod poprzedniego przykładu przerobiony tak by nie korzystać ze struktur "bits":
Code: c
Kod ten korzysta z operatora bitowej negacji oraz alternatywy do zgaszenia najmłodszego bitu z rejestru TRISC (w zasadzie trochę niepotrzebnie) a potem za pomocą operatora XOR zmienia jego stan co 500ms.
Naprawdę bardzo przydaje się tu dobra znajomość operatorów z języka C.
Miganie diodą LED - za pomocą timera/przerwania
W jednym z następnych tematów z tej serii pokażę, że diodą LED można też migać z pomocą timera/przerwania. PIC18F2550 posiada kilka timerów. Jednakże na tym etapie tutoriala ich nie wprowadzam, więc ten przykład poznamy dopiero za jakiś czas.
Wiele diod LED
Pora na nieco bardziej skomplikowane przykłady - pomigamy teraz większą ilością LEDów.
Weźmiemy ich 8, gdyż nasz PIC18F2550 jest ośmiobitowy i co za tym idzie jego porty są ośmiobitowe.
Do każdej diody LED potrzebny jest rezystor ograniczający ich prąd, ja wybrałem wartość 470 omów.
Przygotowujemy wszystko:

Podłączamy diody do płytki, ja wybrałem, że będą miały wspólną masę.
Dla przypomnienia - piny diody LED:

I całość złożona na płytce stykowej:

Do wszystkiego przydadzą się też przewody dla płytek stykowych:

Podłączamy całość do naszego PICa, do jego portu B. Następne kody też będą oparte o ten układ:

Na obrazku zaznaczyłem które dokładnie piny użyłem (cały port B - osiem jego pinów):

UWAGA: Dwa z użytych pinów (RB7 i RB6) pełnią też funkcję podłączenia do ICSP (programatora - PGD i PGC). W tym przypadku nie jest to problemem. Nie odłączam programatora na czas działania kodu. Po prostu w trakcie programowania trochę migają sobie podłączone LEDy, ale nie zakłóca to wgrywania wsadu. Wszystko działa poprawnie.
I na początek - prosty kod migania ośmioma LEDami na PORTB:
Code: c
Rezultat działania kodu na filmie:
Kod raczej nie wymaga dodatkowego komentarza. 0xff to jest 255 w notacji szesnastkowej, czyli w binarnej 0b11111111. Tak też można napisać w kodzie.
Załącznik (miganie diodami na porcie B) do pobrania:
Wiele diod LED - bitowa negacja
Kod migania ośmioma diodami z poprzedniego przykładu można chcieć trochę uprościć, ale trzeba uważać, by nie popełnić błędu.
Można próbować skrócić ten jego fragment:
Code: c
I zamiast ręcznego ustawienia stanów bitów (na wysoki a potem na niski) dać jedną negację, tak:
Code: c
Ale to nie zadziała. Nie możemy tutaj użyć operatora !, gdyż on dla wartości PORTB równej 0 zwróci nam 1 (binarnie: 0b00000001) a nie 0b11111111.
Wtedy kod skutkować będzie miganiem tylko diody LED na RB0:
Należy tutaj użyć negacji bitowej, czyli operatora ~:
Code: c
I od razu jest tak jak powinno:
Negacja bitowa dla wartości 0b00000000 zwróci nam 0b11111111, czyli to, czego oczekujemy. Po prostu zmienia stan każdego bitu na przeciwny.
Dobra znajomość operacji na bitach jest tutaj naprawdę ważna.
Odliczanie binarne
Z rejestrów portów można korzystać tak jak z każdej innej zmiennej. Można do niej dodawać wartość, itp. W ten sposób łatwo można zrobić odliczanie binarne, czyli zegar który odlicza kolejne liczby i wyświetla je binarnie. Wyświetlanie binarne oczywiście zrealizowane jest z pomocą ośmiu diod LED, gdzie każda dioda odpowiada stanowi jednego z bitów (zapalony lub zgaszony).
Zmodyfikowany kod:
Code: c
Załącznik (kod, skompilowany hex, .bat do kompilacji na Windowsa):
Rezultat działania kodu przedstawia filmik:
Filmik pokazuje działanie od początku przez 30 sekund. Wcześniej PIC nie chodził gdyż w PICKIT3 na komputerze trzymałem go w trybie RESET:

W ramach małego przypomnienia zamieszczam tu tabelkę pokazującą liczby od 0 do 15 zapisane dziesiętnie i binarnie (aczkolwiek uważam, że powinno się to znać na pamięć):
Dziesiętnie | Binarnie |
0 | 0000 |
1 | 0001 |
2 | 0010 |
3 | 0011 |
4 | 0100 |
5 | 0101 |
6 | 0110 |
7 | 0111 |
8 | 1000 |
9 | 1001 |
10 | 1010 |
11 | 1011 |
12 | 1100 |
13 | 1101 |
14 | 1110 |
15 | 1111 |
Przesunięcie bitowe
Na rejestrach można wykonywać też oczywiście inne, nieco mniej znane operacje. Na przykład przesunięcie bitowe. W języku C pozwalają na nie operatory >> i <<.
Przesunięcie bitowe (jak sama nazwa wskazuje) przesuwa bity.
Przykładowo, mając taką zawartość w zmiennej:
B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
Po jednokrotnym przesunięciu w lewo otrzymamy:
B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
Wypróbujmy to i rozważmy taki kod:
Code: c
Kod ustawia wartość LATB na 1 (zapalony tylko najmłodszy bit), byśmy mieli co przesuwać, po czym co pół sekundy przesuwa zawartość tego rejestru.
Działanie obrazuje filmik (te zakłócenie na początku jest od PICKITa, przez jego sygnał z RESET):
Filmik pokazuje działanie od początku przez 30 sekund. Po przesunięciu 8 razy niestety już nie pali się żadna dioda LED, gdyż przesuwanie bitowe nie zapętla się.
Oczywiście przesuwać można dowolną kombinację bitów, nie tylko jeden bit.
Tak samo można od razu przesunąć o dwa bity, trzy, itp..
Załącznik (kod, skompilowany hex, .bat do kompilacji na Windowsa):
Przesunięcie bitowe - zapętlenie
Powyższy kod z przesunięciem można bardzo łatwo "zapętlić" - wystarczy dodać warunek, który przywraca stan początkowy portu B po wykonaniu całego przesunięcia.
Czyli na przykład coś takiego:
Code: c
Ustawia stan początkowy gdy wszystko zostanie już ''''wyprzesuwane'''' na zewnątrz.
Cały kod po zmianie:
Code: c
Rezultat działania na filmiku:
Załącznik (kod, skompilowany hex, .bat do kompilacji na Windowsa):
Przycisk czyli rezystory pull-up i pull-down
Do tej pory omówiliśmy tylko tryb wyjścia (output) pinów.
Ale jest jeszcze tryb wejścia - input. Pozwala on odczytać stan logiczny (zero lub jeden) na danym pinie.
Potem można użyć np. instrukcji warunkowej if by wykonać coś, jeśli np. przycisk jest wciśnięty.
Przygotujmy zatem najprostszy układ z przyciskiem.
Potrzebne nam będzie:
- przycisk - oczywiście, tzw. tactile switch
- rezystor rzędu 10k, aby podciągnąć pin mikrokontrolera do masy bądź zasilania.


Przypominam pinout przycisku:

Po co rezystor? Rezystor jest potrzebny ponieważ pin mikrokontrolera zostawiony ''''w powietrzu'''' ma stan nieoznaczony i łapie tylko zakłócenia.
Podłączenie pinu przez rezystor do masy lub zasilania pozwala nadać mu domyślny stan (zero lub jeden), który potem zmienimy przyciskiem.
Taki rezystor nazywa się pull-up lub pull-down.
Przycisk z pull-up wygląda tak:

To jest przycisk z rezystorem pull-up. Pin IO mikrokontrolera podciągnięty jest do zasilania i ma domyślnie stan logiczne 1. Dopiero w momencie wciśnięcia przycisku jest zwierany do masy i przechodzi w stan 0.
Przycisk z pull-down wygląda tak:

To jest przycisk z rezystorem pull-down. Pin IO mikrokontrolera podciągnięty jest do masy i ma domyślnie stan logiczne 0. Dopiero w momencie wciśnięcia przycisku jest zwierany do zasilania i przechodzi w stan 1.
Przez ten rezystor 10k cały czas płynie bardzo mały prąd.
Prosty przykład z przyciskiem
Wrócimy teraz do przykładu ''''prosty blink'''', który tylko mruga diodą na pinie RC0 i zmodyfikujemy go tak, by przełączał stan diody jak wciśniemy przycisk.
Stary kod migania diodą LED (stanowi dla nas punkt startowy):
Code: c
Wybierzemy sobie, że przycisk damy na pinie RC2:

I będzie to przycisk z rezystorem pull-up:

Przygotowujemy podłączenie na płytce - dodałem rezystor, przycisk i dwie zworki by wszystko połączyć:


I pora napisać kod.
Musimy:
- zainicjować pin RC2 jako wejście
- w pętli odczytywać stan RC2 i jeśli jest wciśnięty (niski) to zmieniać stan RC0
Gotowy kod wygląda tak:
Code: c
Załącznik (kod, skompilowany hex, .bat do kompilacji na Windowsa):
Rezultat na filmie:
Działa, ale tutaj warto poruszyć bardzo ważną kwestię...
Problem drgania styków i debouncing styków
Sprytny czytelnik zauważy, że zastosowane w poprzednim akapicie przeze mnie tu rozwiązanie nie jest idealne - blokuje ono całkiem wykonywanie pętli z funkcji main na całe 500 ms po wciśnięciu przycisku! Na początku nauki można sobie na to pozwolić, zwłaszcza że dzięki temu możemy uciec od tzw. problemu drgań styków i robienia debouncingu, ale w finalnym produkcie takie coś jest niedopuszczalne.
Problem drgań styków wynika stąd, że w rzeczywistym świecie elementy nie są idealne i przy wciskaniu przycisku nie uzyskujemy pojedynczego, wzorowego przejścia ze stanu niski na wysoki (lub na odwrót), tylko takie przejście w zakłóconej formie.
Problem ten można rozwiązywać zasadniczo na dwa sposoby:
- software''''owo - czyli w kodzie, poprzez różne sztuczki z ponownym sprawdzeniem przycisku i oczekiwaniem
- hardware''''owo, czyli sprzętowo - np. poprzez zastosowanie kondensatora, filtru
To jest duży temat, nadający się aż na całkiem osobny post, więc na razie odeślę tylko do dokumentu PDF autorstwa mojego korespondenta z USA Jack Ganssle:
Oczywiście umieszczam tu PDFa za jego pozwoleniem.
W dalszych częściach tutorialu jeszcze do tego tematu wrócę.
Przycisk i 8 diod LED - program losujący
W ramach ćwiczenia jeszcze połączymy dwa wcześniej przedstawione przykłady, czyli:
- przykład z przyciskiem na RC2
- przykład z 8 diodami LED na porcie B
Dodatkowo tutaj skorzystamy z faktu, że przyciski nie są idealne (istnieje drganie styków) a człowiek ma kłopot z tym, by trzymać przycisk zawsze idealnie tak samo długo.
Zrobimy program-kostka. Będzie losować liczbę od 0 do 255, którą wyświetlimy binarnie.
Dodamy jakąś własną zmienną, ustalimy jej na początku jakąś wartość, którą potem będziemy zmieniać w trakcie trzymania przycisku. Wszystko najprościej jak się da - dla użytkowników i tak rezultat będzie się zdawać losowy.
Oto kod:
Code: c
Rezultat działania:
Wylosowaną liczbę można odczytać binarnie z LEDów.
Oczywiście kod można by znacznie ulepszyć, zastosować jakiś poprawny algorytm generowania liczb pseudolosowych itp, ale nie to było celem tego przykładu. Miało być proste i jest proste.
Załącznik (kod, skompilowany hex, .bat do kompilacji na Windowsa):
Wewnętrzne pull up dla portu B
PIC18F2550 oferuje wewnętrzne, programowalne rezystory weak pull-up na porcie B. Pozwalają nam one uniknąć stosowania rezystorów w naszym układzie i podłączyć przycisk po prostu tak:

Oczywiście nie oznacza to, że tego rezystora pull-up w tym układzie nie ma - on jest, ale wewnątrz PICa.
Więc z naszej płytki stykowej możemy usunąć rezystor i zostawić tylko przycisk, który podłączymy na, powiedzmy, RB0:


Rezystory pull-up na porcie B włącza się wszystkie na raz, nie da się ustawić poszczególnie każdego z nich. Ale nie są one aktywne jeśli dany pin z portu B jest w trybie wyjścia a nie wejścia.
Odpowiada za nie bit RPBU z rejestru INTCON2:

Jako że port RB0 ma też funkcję AN12, to również trzeba zadbać (tak jak pisałem na początku tematu) by ją mu wyłączyć.
Bazując na tym co napisałem można opracować kod:
Code: c
Kod oczywiście działa poprawnie i pozwala na korzystanie z przycisku bez zewnętrznego rezystora, oto filmik z działania:
Może ktoś z czytelników podejmie się próby ulepszenia tego kodu i zrobi go w wersji która nie blokuje głównej pętli na pół sekundy?
Załącznik (kod, skompilowany hex, .bat do kompilacji na Windowsa):
Przycisk i dioda LED na jednym pinie
Tutaj chciałbym jeszcze pokazać, że tryb każdego pinu (rejestr TRIS*) można modyfikować dowolnie już w trakcie działania programu. Nic nie narzuca, że trzeba to robić poza główną pętlą.
Spróbujemy wykorzystać tą możliwość by zrealizować obsługę diody LED i przycisku na jednym pinie IO mikrokontrolera.
Po prostu raz pin będzie w trybie wejścia, a raz w trybie wyjścia.
Użyjemy pinu RC2.
Do pinu podłączymy i przycisk, i diodę.
Proponuję coś takiego:

Ten układ zrobiony u mnie na płytce stykowej wygląda tak:


Do tego obsługi potrzebny jest odpowiedni kod. Kod powinien na bieżąco zmieniać tryb pinu, raz na wejście, i raz na wyjście i odpowiednio nim sterować. Przykładowo:
Code: c
Wartości delay można tu lepiej dobrać, a w ramach ćwiczenia polecam usprawnić ten kod i jakoś lepiej zrealizować oczekiwanie/debouncing.
I na koniec filmik pokazujący, że to działa:
Oczywiście, tak zrealizowany układ zapala siłą rzeczy diodę w trakcie działania, ale dla nas to nie jest problem.
Załącznik (kod, skompilowany hex, .bat do kompilacji na Windowsa):
Dwie diody LED na jednym pinie
Na koniec jeszcze chciałbym podkreślić, że diodę LED można podłączyć zarówno między pinem wyjścia a masą, jak również między pinem wyjścia a zasilaniem. Piny PIC18F2550 mają te same wartości sink i source current.
Można też podłączyć dwie diody na raz, w ten sposób:

W praktyce wygląda to tak:


Wtedy możemy użyć niezmienione kodu z ''''blink LED'''' i będzie on migać diodami na przemian:
Code: c
Filmik z działania:
Można też zapalić obie diody na raz, w ten sposób:
Code: c
Niestety wtedy będą świecić o wiele słabiej, czego oczywiście można się spodziewać po samym ich sposobie podłączenia.
Podsumowanie
W tej części omówiliśmy najprostsze operacje cyfrowego IO PIC18F2550 na wielu prostych przykładach. W dalszych częściach tutoriala zapewne zajmiemy się już bardziej szczegółowo powiązanymi z tym kwestiami, np. obsługą klawiatury matrycowej, debouncingiem przycisków, zrobimy charlieplexing dla diod LED, itp. W nadchodzącej niedługo części zapewne poznamy też możliwości konfiguracji oscylatora PICa (do tej pory korzystamy z wewnętrznego 1MHz) i timerów oraz przerwań.
PS: Ten temat to nie jest finalna wersja. Będzie z czasem uzupełniany i ulepszany.
Pierwszy temat z serii został zmodyfikowany i zaktualizowany, m. in. o schemat połączeń, o definicję ICSP (troszke jej brakowało), itp. Oba tematy będą na bieżąco poprawiane i redagowane, jak ktoś ma jakieś uwagi, zapraszam na PW.
Cool? Ranking DIY