- Dlaczego Timer, skoro mamy Delay ()?
- Zegary mikrokontrolera PIC:
- Programowanie i objaśnienie robocze:
- Schemat obwodu i symulacja Proteusa:
Będzie to piąty samouczek z naszej serii samouczków PIC, który pomoże ci nauczyć się i używać timerów w PIC16F877A. W naszych poprzednich samouczkach zaczęliśmy od wprowadzenia do PIC i MPLABX IDE, następnie napisaliśmy nasz pierwszy program PIC do mrugania diodą LED za pomocą PIC, a następnie wykonaliśmy sekwencję migania diody LED za pomocą funkcji opóźnienia w mikrokontrolerze PIC. Teraz użyjmy tej samej sekwencji migania diod LED, której używaliśmy w poprzednim samouczku, a dzięki temu nauczymy się używać timerów w naszym MCU PIC. Właśnie dodaliśmy jeszcze jeden przycisk na tablicy LED na potrzeby tego samouczka. Przejdź przez samouczek, aby dowiedzieć się więcej.
Timery są jednym z najważniejszych mechanizmów roboczych dla wbudowanego programisty. Każda aplikacja, którą projektujemy, będzie w jakiś sposób obejmowała aplikację czasową, taką jak włączanie lub wyłączanie czegoś po określonym czasie. Dobrze, ale po co nam liczniki czasu, skoro mamy już makra opóźnienia (__delay_ms ()) robiące to samo !!
Dlaczego Timer, skoro mamy Delay ()?
Makro opóźnienia jest nazywane opóźnieniem „zrzutu”. Ponieważ podczas wykonywania funkcji opóźnienia MCU umieszcza zrzut, po prostu tworząc opóźnienie. Podczas tego procesu MCU nie może słuchać swoich wartości ADC ani odczytywać niczego ze swoich rejestrów. Dlatego nie zaleca się korzystania z funkcji opóźnienia, z wyjątkiem zastosowań, takich jak miganie diody LED, w przypadku których opóźnienie czasowe nie musi być dokładne ani długie.
Makra opóźnienia mają również następujące wady,
- Wartość opóźnienia musi być stała dla makr opóźnienia; nie można jej zmienić podczas wykonywania programu. Stąd pozostaje to zdefiniowane przez programistę.
- Opóźnienie nie będzie dokładne w porównaniu z używaniem timerów.
- Większe wartości opóźnień nie mogą być tworzone za pomocą makr, na przykład opóźnienie półgodzinne nie może być tworzone przez makra opóźnienia. Maksymalne opóźnienie, którego można użyć, zależy od zastosowanego oscylatora kwarcowego.
Zegary mikrokontrolera PIC:
Fizycznie czasomierz jest rejestrem, którego wartość stale rośnie do 255, a następnie zaczyna się od nowa: 0, 1, 2, 3, 4… 255…. 0, 1, 2, 3……itp.
PIC16F877A PIC MCU ma trzy moduły programatora. Są to nazwy Timer0, Timer1 i Timer2. Timer 0 i Timer 2 to zegary 8-bitowe, a Timer 1 to zegary 16-bitowe. W tym samouczku będziemy używać Timera 0 w naszej aplikacji. Kiedy zrozumiemy Timer 0, łatwo będzie pracować z Timerem 1 i Timerem 2.
Timer / licznik modułu Timer0 posiada następujące cechy:
- 8-bitowy zegar / licznik
- Czytelny i zapisywalny
- 8-bitowy programowalny preskaler
- Wybór zegara wewnętrznego lub zewnętrznego
- Przerwanie przy przepełnieniu z FFh do 00h
- Wybór krawędzi dla zewnętrznego zegara
Aby zacząć używać timera, powinniśmy zrozumieć niektóre z wymyślnych terminów, takich jak 8-bitowy / 16-bitowy zegar, Prescaler, Przerwania timera i Focs. Zobaczmy teraz, co naprawdę oznacza każdy z nich. Jak wspomniano wcześniej, w naszym mikrokontrolerze PIC znajdują się zarówno 8-bitowe, jak i 16-bitowe timery, główna różnica między nimi polega na tym, że 16-bitowy zegar ma znacznie lepszą rozdzielczość niż 8-bitowy zegar.
Prescaler to nazwa części mikrokontrolera, która dzieli zegar oscylatora zanim osiągnie logikę zwiększającą stan timera. Zakres id preskalera wynosi od 1 do 256, a wartość preskalera można ustawić za pomocą rejestru OPTION (tego samego, którego używaliśmy do rezystorów podciągających). Np. Jeśli wartość preskalera wynosi 64, to dla każdego 64- tego impulsu Timer będzie zwiększany o 1.
Gdy licznik czasu zwiększa się i osiąga maksymalną wartość 255, wyzwoli przerwanie i ponownie zainicjuje się do 0. To przerwanie jest nazywane przerwaniem timera. To przerwanie informuje MCU, że ten konkretny czas minął.
Fosc oznacza Częstotliwość oscylatora, to częstotliwość kryształu używany. Czas potrzebny dla rejestru Timera zależy od wartości Prescalera i wartości Fosc.
Programowanie i objaśnienie robocze:
W tym samouczku ustawimy dwa przyciski jako dwa wejścia i 8 diod LED jako 8 wyjść. Pierwszy przycisk zostanie użyty do ustawienia opóźnienia czasowego (500 ms na każde naciśnięcie), a drugi przycisk zostanie użyty do rozpoczęcia migania sekwencji timera. Na przykład, jeśli pierwszy przycisk zostanie naciśnięty trzykrotnie (500 * 3 = 1500 ms), opóźnienie zostanie ustawione na 1,5 sekundy, a po naciśnięciu drugiego przycisku każda dioda LED zaświeci się i wyłączy z określonym opóźnieniem. Obejrzyj film demonstracyjny na końcu tego samouczka.
Mając teraz na uwadze te podstawy, spójrzmy na nasz program podany na końcu w sekcji Kod.
W porządku, jeśli nie dostałeś programu, ale jeśli tak! Daj sobie plik cookie i zrzuć program, aby cieszyć się wynikami. Dla innych podzielę program na znaczące części i wyjaśnię, co się dzieje w każdym bloku.
Jak zawsze, kilka pierwszych wierszy kodu to ustawienia konfiguracji i pliki nagłówkowe, nie będę tego wyjaśniał, ponieważ zrobiłem to już w moich poprzednich tutorialach.
Następnie pomińmy wszystkie linie i wskoczmy prosto do funkcji void main, w której mamy konfigurację PORT dla Timer0.
void main () {/ ***** Konfiguracja portu dla timera ****** / OPTION_REG = 0b00000101; // Timer0 z zewnętrznym freq i 64 jako prescalar // Umożliwia również PULL UPs TMR0 = 100; // Załaduj wartość czasu dla 0,0019968 s; delayValue może zawierać się w przedziale 0-256, tylko TMR0IE = 1; // Włącz bit przerwania timera w rejestrze PIE1 GIE = 1; // Włącz globalne przerwanie PEIE = 1; // Włącz przerwanie peryferyjne / *********** ______ *********** /
Aby to zrozumieć, musimy spojrzeć na Rejestr OPCJI w naszym arkuszu danych PIC.
Jak omówiono w poprzednim samouczku, bit 7 jest używany do włączania słabego rezystora podciągającego dla PORTB. Spójrz na powyższy rysunek, bit 3 jest ustawiony na 0, aby poinstruować MCU, że następujący preskaler, który jest ustawiany, powinien być używany dla timera, a nie dla WatchDogTimer (WDT). Tryb timera jest wybierany przez kasowanie bitu 5 T0CS
(OPTION_REG <5>)
Teraz bity2-0 są używane do ustawienia wartości preskalera dla timera. Jak pokazano w powyższej tabeli, aby ustawić wartość preskalera na 64, bity muszą być ustawione na 101.
Następnie przyjrzyjmy się rejestrom skojarzonym z Timer0
Timer rozpocznie zwiększanie wartości po ustawieniu i przepełnieniu po osiągnięciu wartości 256, aby umożliwić przerwanie timera w tym momencie, rejestr TMR0IE musi być ustawiony wysoko. Ponieważ sam Timer 0 jest urządzeniem peryferyjnym, musimy włączyć Peripheral Interrupt, ustawiając PEIE = 1. Na koniec musimy włączyć Global Interrupt, aby MCU był powiadamiany o przerwaniu podczas dowolnej operacji, odbywa się to przez ustawienie GIE = 1.
Opóźnienie = ((256-REG_val) * (Prescal * 4)) / Fosc
Powyższy wzór służy do obliczenia wartości opóźnienia.
Gdzie
REG_val = 100;
Prescal = 64
Fosc = 20000000
To na obliczeniach daje, Opóźnienie = 0,0019968 s
Następnym zestawem linii jest ustawienie portów we / wy.
/ ***** Konfiguracja portu dla I / O ****** / TRISB0 = 1; // Poinstruuj MCU, że pin 0 PORTB jest używany jako wejście dla przycisku 1. TRISB1 = 1; // Poinstruuj MCU, że pin 1 PORTB jest używany jako wejście dla przycisku 1. TRISD = 0x00; // Poinstruuj MCU, że wszystkie piny na PORT D są wyprowadzane PORTD = 0x00; // Zainicjuj wszystkie piny na 0 / *********** ______ *********** /
To jest to samo, co w naszym poprzednim samouczku, ponieważ używamy tego samego sprzętu. Tyle że dodaliśmy kolejny przycisk jako wejście. Odbywa się to za pomocą linii TRISB1 = 1.
Następnie, nieskończona pętla while na lewą stronę mamy dwa bloki kodu. Jeden służy do pobierania danych wejściowych timera od użytkownika, a drugi do wykonywania sekwencji opóźnienia na diodach LED. Wyjaśniłem je, używając komentarzy w każdej linii.
while (1) {count = 0; // Nie uruchamiaj licznika czasu w głównej pętli // ******* Uzyskaj opóźnienie numeru od użytkownika **** ////// if (RB0 == 0 && flag == 0) // Kiedy dane wejściowe podane {get_scnds + = 1; // get_scnds = get_scnds + http: // Flaga zmiennej przyrostowej = 1; } if (RB0 == 1) // Aby zapobiec ciągłemu zwiększaniu flag = 0; / *********** ______ *********** /
Zmienna o nazwie get_scnds jest zwiększana za każdym razem, gdy użytkownik naciśnie przycisk 1. Zmienna flaga (zdefiniowana programowo) jest używana do wstrzymania procesu zwiększania, dopóki użytkownik nie usunie swojego palca z przycisku.
// ******* Wykonaj sekwencję z opóźnieniem **** ////// while (RB1 == 0) {PORTD = 0b00000001 <
Następny blok zostaje uruchomiony, jeśli zostanie naciśnięty drugi przycisk. Ponieważ użytkownik zdefiniował już wymagane opóźnienie czasowe za pomocą przycisku pierwszego i zostało zapisane w zmiennej get_scnds. Używamy zmiennej o nazwie hscnd, ta zmienna jest kontrolowana przez ISR (procedura obsługi przerwania).
Procedura obsługi przerwania to przerwanie, które będzie wywoływane za każdym razem, gdy Timer0 przepełnia. Zobaczmy, jak jest kontrolowany przez ISR w następnym bloku, tak jak chcemy zwiększać opóźnienie o pół sekundy (0,5 s) po każdym naciśnięciu przycisku, a następnie musimy zwiększać zmienną hscnd co pół sekundy. Ponieważ zaprogramowaliśmy nasz zegar tak, aby przepełniał co 0,0019968 s (~ 2 ms), więc aby policzyć pół sekundy, zmienna licznika powinna wynosić 250, ponieważ 250 * 2 ms = 0,5 sekundy. Więc kiedy count osiągnie 250 (250 * 2ms = 0,5 sekundy), oznacza to, że minęło pół sekundy, więc zwiększamy hscnd o 1 i inicjalizujemy count do zera.
void breaking timer_isr () {if (TMR0IF == 1) // Flaga timera została wyzwolona z powodu przepełnienia timera {TMR0 = 100; // Załaduj licznik czasu Wartość TMR0IF = 0; // Wyczyść licznik przerwania licznika czasu ++; } if (liczba == 250) {hscnd + = 1; // hscnd będzie zwiększane co pół sekundy count = 0; }}
Więc używamy tej wartości i porównujemy ją z naszym hscnd i przesuwamy naszą diodę LED na podstawie czasu zdefiniowanego przez użytkownika. Jest również bardzo podobny do poprzedniego samouczka.
To wszystko, mamy zrozumiany i działający nasz program.
Schemat obwodu i symulacja Proteusa:
Jak zwykle sprawdźmy najpierw wyjście za pomocą Proteusa, tutaj załączyłem pliki schematów Proteusa.
Dodaj przycisk do naszej poprzedniej tablicy LED, a nasz sprzęt jest gotowy do pracy. Powinien wyglądać mniej więcej tak:
Po nawiązaniu połączenia prześlij kod i sprawdź dane wyjściowe. Jeśli masz jakiś problem, skorzystaj z sekcji komentarzy. Sprawdź również wideo poniżej, aby zrozumieć cały proces.