Spisu treści:
- Krok 1: Konstruowanie obwodu
- Krok 2: Dlaczego potrzebujemy komentarzy i pliku M328Pdef.inc?
- Krok 3: Blink.asm
- Krok 4: Dyrektywy asemblera.org
- Krok 5: Zegar/licznik 0
- Krok 6: Obsługa przepełnienia
- Krok 7: Opóźnienie
- Krok 8: Mrugnij
- Krok 9: Wniosek
Wideo: Samouczek asemblera AVR 3: 9 kroków
2024 Autor: John Day | [email protected]. Ostatnio zmodyfikowany: 2024-01-30 11:26
Witamy w samouczku numer 3!
Zanim zaczniemy, chciałbym poruszyć kwestię filozoficzną. Nie bój się eksperymentować z obwodami i kodem, który tworzymy w tych samouczkach. Zmieniaj przewody, dodawaj nowe komponenty, wyjmuj komponenty, zmieniaj wiersze kodu, dodawaj nowe wiersze, usuwaj wiersze i zobacz, co się stanie! Bardzo trudno jest cokolwiek złamać, a jeśli to zrobisz, kogo to obchodzi? Nic, czego używamy, w tym mikrokontroler, nie jest bardzo drogie i zawsze kształci się, jak coś może zawieść. Nie tylko dowiesz się, czego nie robić następnym razem, ale, co ważniejsze, będziesz wiedział, dlaczego tego nie robić. Jeśli jesteś podobny do mnie, kiedy byłeś dzieckiem i dostałeś nową zabawkę, nie minęło dużo czasu, zanim miałeś ją w kawałkach, aby zobaczyć, co sprawiło, że działa dobrze? Czasami zabawka była nieodwracalnie uszkodzona, ale nic wielkiego. Pozwolenie dziecku na odkrywanie swojej ciekawości, nawet do zepsutych zabawek, zamienia go w naukowca lub inżyniera zamiast w zmywarkę.
Dzisiaj zamierzamy okablować bardzo prosty obwód, a następnie trochę zagłębić się w teorię. Przepraszamy za to, ale potrzebujemy narzędzi! Obiecuję, że nadrobimy to w samouczku 4, gdzie będziemy robić poważniejsze budowanie obwodów, a wynik będzie całkiem fajny. Jednak sposób, w jaki musisz wykonać wszystkie te samouczki, jest bardzo powolny, kontemplacyjny. Jeśli po prostu przebrniesz, zbudujesz obwód, skopiujesz i wkleisz kod, a następnie go uruchomisz, na pewno zadziała, ale niczego się nie nauczysz. Musisz pomyśleć o każdej linii. Pauza. Eksperyment. Wynaleźć. Jeśli zrobisz to w ten sposób, pod koniec piątego samouczka przestaniesz budować fajne rzeczy i nie będziesz potrzebować więcej korepetycji. W przeciwnym razie po prostu obserwujesz, a nie uczysz się i tworzysz.
W każdym razie wystarczy filozofia, zaczynajmy!
W tym samouczku będziesz potrzebować:
- Twoja tablica prototypowa
- dioda LED
- przewody łączące
- rezystor około 220 do 330 omów
- Instrukcja zestawu instrukcji: www.atmel.com/images/atmel-0856-avr-instruction-se…
- Karta katalogowa: www.atmel.com/images/Atmel-8271-8-bit-AVR-Microco…
- inny oscylator kwarcowy (opcjonalnie)
Oto link do pełnej kolekcji samouczków:
Krok 1: Konstruowanie obwodu
Obwód w tym samouczku jest niezwykle prosty. Zasadniczo napiszemy program "mrugnięcia", więc wszystko, czego potrzebujemy, to co następuje.
Podłącz diodę LED do PD4, następnie do rezystora 330 omów, a następnie do uziemienia. tj.
PD4 - LED - R(330) - GND
i to wszystko!
Teoria będzie jednak trudna do wytrwania…
Krok 2: Dlaczego potrzebujemy komentarzy i pliku M328Pdef.inc?
Myślę, że powinniśmy zacząć od pokazania, dlaczego plik dołączany i komentarze są pomocne. Żadne z nich nie są właściwie potrzebne i możesz bez nich pisać, asemblować i przesyłać kod w ten sam sposób, a będzie on działał doskonale (chociaż bez pliku dołączanego możesz otrzymać pewne skargi od asemblera - ale bez błędów)
Oto kod, który napiszemy dzisiaj, poza tym, że usunąłem komentarze i plik include:
.urządzenie ATmega328P
.org 0x0000 jmp a.org 0x0020 jmp ea: ldi r16, 0x05 out 0x25, r16 ldi r16, 0x01 sts 0x6e, r16 sei clr r16 out 0x26, r16 sbi 0x0a, 0x04 sbi 0x0b, 0x04 b:cbi 0 cbi 0x0b, 0x04 rcall c rjmp bc: clr r17 d: cpi r17, 0x1e brne d ret e: inc r17 cpi r17, 0x3d brne PC+2 clr r17 reti
całkiem proste, prawda? Ha ha. Jeśli zmontowałeś i przesłałeś ten plik, spowodujesz, że dioda LED będzie migać w tempie 1 mignięcia na sekundę, z miganiem trwającym 1/2 sekundy i przerwą między błyskami trwającą 1/2 sekundy.
Jednak patrzenie na ten kod nie jest pouczające. Jeśli miałbyś pisać taki kod i chciałbyś go zmodyfikować lub zmienić jego przeznaczenie w przyszłości, miałbyś trudności.
Wstawmy więc komentarze i dołączmy plik z powrotem, abyśmy mogli to zrozumieć.
Krok 3: Blink.asm
Oto kod, który omówimy dzisiaj:
;************************************
; napisane przez: 1o_o7; Data:; wersja: 1.0; plik zapisany jako: blink.asm; dla AVR: atmega328p; częstotliwość zegara: 16MHz (opcjonalnie);***********************************; Funkcja programu:---------------------; odlicza sekundy miganiem diody LED;; PD4 - LED - R(330 om) - GND;;---------------------------.nolist.include "./m328Pdef.inc".lista;==============; Deklaracje:.def temp = r16.def przepełnienia = r17.org 0x0000; pamięć (PC) lokalizacja obsługi resetowania rjmp Reset; jmp kosztuje 2 cykle procesora, a rjmp tylko 1; więc chyba że potrzebujesz skoczyć więcej niż 8k bajtów; potrzebujesz tylko rjmp. Niektóre mikrokontrolery dlatego tylko; mieć rjmp, a nie jmp.org 0x0020; lokalizacja pamięci obsługi przepełnienia Timera0 rjmp overflow_handler; przejdź tutaj, jeśli wystąpi przerwanie przepełnienia timera0;============ Reset: ldi temp, 0b00000101 out TCCR0B, temp; ustaw bity selektora zegara CS00, CS01, CS02 na 101; ustawia to Timer Counter0, TCNT0 w tryb FCPU/1024; więc tyka na CPU freq/1024 ldi temp, 0b00000001 sts TIMSK0, temp; ustaw bit Timer Overflow Interrupt Enable (TOIE0); rejestru masek przerwań czasowych (TIMSK0) sei; włącz przerwania globalne - równoważne "sbi SREG, I" clr temp out TCNT0, temp; inicjalizuj Timer/Licznik na 0 sbi DDRD, 4; ustaw PD4 na wyjście;======================; Główna część programu: miganie: sbi PORTD, 4; włącz diodę LED na opóźnienie wywołania PD4; opóźnienie wyniesie 1/2 sekundy cbi PORTD, 4; wyłącz diodę LED na opóźnieniu wywołania PD4; opóźnienie wyniesie 1/2 sekundy rjmp miga; pętla z powrotem do opóźnienia startu: clr overflows; ustaw overflows na 0 sec_count: cpi overflows, 30; porównaj liczbę przepełnień i 30 brne sec_count; branch, aby powrócić do sec_count, jeśli nie jest równy ret; jeśli wystąpiło 30 przepełnień wróć do blink overflow_handler: inc przepełnienia; dodaj 1 do przepełnień przepełnienia zmiennej cpi, 61; porównaj z 61 brne PC+2; Licznik programu + 2 (pomiń następny wiersz), jeśli nie jest równy przepełnienia clr; jeśli wystąpiło 61 przepełnień, wyzeruj licznik reti; powrót z przerwania
Jak widać, moje komentarze są teraz nieco krótsze. Kiedy już wiemy, co robią polecenia w zestawie instrukcji, nie musimy wyjaśniać tego w komentarzach. Trzeba tylko wyjaśnić, co się dzieje z punktu widzenia programu.
Będziemy omawiać, co to wszystko robi kawałek po kawałku, ale najpierw spróbujmy uzyskać globalną perspektywę. Główna część programu działa w następujący sposób.
Najpierw ustawiamy bit 4 PORTD za pomocą „sbi PORTD, 4”, co powoduje wysłanie 1 do PD4, który ustawia napięcie na 5V na tym pinie. To włączy diodę LED. Następnie przeskakujemy do podprogramu "opóźnienie", który odlicza 1/2 sekundy (później wyjaśnimy, jak to robi). Następnie powracamy do migania i kasowania bitu 4 na PORTD, który ustawia PD4 na 0 V, a tym samym wyłącza diodę LED. Następnie opóźniamy o kolejne 1/2 sekundy, a następnie ponownie przeskakujemy do początku migania z "rjmp blink".
Powinieneś uruchomić ten kod i zobaczyć, że robi to, co powinien.
I masz to! To wszystko, co ten kod robi fizycznie. Wewnętrzna mechanika tego, co robi mikrokontroler, jest nieco bardziej skomplikowana i dlatego robimy ten samouczek. Omówmy więc kolejno każdą sekcję.
Krok 4: Dyrektywy asemblera.org
Wiemy już, co robią dyrektywy asemblera.nolist,.list,.include i.def z naszych poprzednich samouczków, więc najpierw spójrzmy na 4 wiersze kodu, które następują po nich:
.org 0x0000
jmp Reset.org 0x0020 jmp overflow_handler
Instrukcja.org mówi asemblerowi, gdzie w "Pamięci programu" ma umieścić następną instrukcję. Podczas wykonywania programu "Licznik programu" (w skrócie PC) zawiera adres aktualnie wykonywanej linii. Więc w tym przypadku, gdy komputer jest w stanie 0x0000, zobaczy polecenie „jmp Reset” znajdujące się w tej lokalizacji pamięci. Powodem, dla którego chcemy umieścić jmp Reset w tym miejscu, jest to, że kiedy program się uruchamia lub układ jest resetowany, komputer zaczyna wykonywać kod w tym miejscu. Tak więc, jak widzimy, właśnie powiedzieliśmy mu, aby natychmiast „przeskoczył” do sekcji oznaczonej „Resetuj”. Dlaczego to zrobiliśmy? Oznacza to, że ostatnie dwie linijki powyżej są po prostu pomijane! Czemu?
No cóż, właśnie wtedy robi się ciekawie. Teraz będziesz musiał otworzyć przeglądarkę plików PDF z pełnym arkuszem danych ATmega328p, który wskazałem na pierwszej stronie tego samouczka (dlatego jest to punkt 4 w sekcji "będziesz potrzebował"). Jeśli twój ekran jest zbyt mały lub masz już zbyt wiele otwartych okien (tak jak w przypadku mnie), możesz zrobić to, co robię i umieścić go na czytniku Ereader lub telefonie z Androidem. Będziesz go używać cały czas, jeśli planujesz pisać kod asemblera. Fajną rzeczą jest to, że wszystkie mikrokontrolery są zorganizowane w bardzo podobny sposób, więc kiedy przyzwyczaisz się do czytania arkuszy danych i kodowania z nich, okaże się, że zrobienie tego samego dla innego mikrokontrolera będzie prawie trywialne. Uczymy się więc w pewnym sensie korzystać ze wszystkich mikrokontrolerów, a nie tylko z atmega328p.
OK, przejdź do strony 18 w arkuszu danych i spójrz na rysunek 8-2.
W ten sposób skonfigurowana jest pamięć programu w mikrokontrolerze. Widać, że zaczyna się od adresu 0x0000 i jest podzielony na dwie sekcje; sekcja flash aplikacji i sekcja flash boot. Jeśli odniesiesz się krótko do strony 277, tabela 27-14, zobaczysz, że sekcja flashowania aplikacji zajmuje lokalizacje od 0x0000 do 0x37FF, a sekcja flashowania rozruchowego zajmuje pozostałe lokalizacje od 0x3800 do 0x3FFF.
Ćwiczenie 1: Ile lokalizacji znajduje się w pamięci Programu? Tj. przekonwertuj 3FFF na dziesiętny i dodaj 1, ponieważ zaczynamy liczyć od 0. Ponieważ każda lokalizacja pamięci ma szerokość 16 bitów (lub 2 bajty), jaka jest całkowita liczba bajtów pamięci? Teraz przekonwertuj to na kilobajty, pamiętając, że w kilobajtach jest 2^10 = 1024 bajty. Sekcja boot flash zmienia się z 0x3800 na 0x37FF, ile to kilobajtów? Ile kilobajtów pamięci pozostało nam do wykorzystania do przechowywania naszego programu? Innymi słowy, jak duży może być nasz program? Ile możemy mieć linijek kodu?
W porządku, teraz, gdy wiemy już wszystko o organizacji pamięci programu flash, kontynuujmy naszą dyskusję na temat oświadczeń.org. Widzimy, że pierwsza lokalizacja pamięci 0x0000 zawiera naszą instrukcję, aby przejść do naszej sekcji, którą nazwaliśmy Reset. Teraz widzimy, co robi instrukcja „.org 0x0020”. Mówi, że chcemy, aby instrukcja w następnym wierszu była umieszczona w lokalizacji pamięci 0x0020. Instrukcja, którą tam umieściliśmy, jest skokiem do sekcji w naszym kodzie, którą oznaczyliśmy etykietą „overflow_handler”… teraz po co, u licha, żądać umieszczenia tego skoku w lokalizacji pamięci 0x0020? Aby się tego dowiedzieć, przejdźmy do strony 65 w arkuszu danych i spójrz na tabelę 12-6.
Tabela 12-6 jest tabelą „Wektorów resetowania i przerwań” i pokazuje dokładnie, dokąd pójdzie komputer, gdy otrzyma „przerwanie”. Na przykład, jeśli spojrzysz na wektor numer 1. „Źródłem” przerwania jest „RESET”, który jest zdefiniowany jako „Zewnętrzny pin, reset po włączeniu, resetowanie Brown-out i reset systemu Watchdog”, co oznacza, jeśli którykolwiek z te rzeczy przydarzają się naszemu mikrokontrolerowi, komputer zacznie wykonywać nasz program w lokalizacji pamięci programu 0x0000. A co z naszą dyrektywą.org? Cóż, umieściliśmy polecenie w lokalizacji pamięci 0x0020 i jeśli spojrzysz w dół tabeli, zobaczysz, że jeśli wystąpi przepełnienie Timera/Counter0 (pochodzące z TIMER0 OVF), wykona on wszystko, co znajduje się w lokalizacji 0x0020. Więc za każdym razem, gdy tak się stanie, komputer przeskoczy do miejsca, które oznaczyliśmy jako „overflow_handler”. Fajnie prawda? Za chwilę zobaczysz, dlaczego to zrobiliśmy, ale najpierw zakończmy ten krok samouczka na bok.
Jeśli chcemy, aby nasz kod był bardziej schludny i uporządkowany, powinniśmy naprawdę zastąpić 4 linijki, które obecnie omawiamy, następującymi (patrz strona 66):
.org 0x0000
rjmp Reset; PC = 0x0000 reti; PC = 0x0002 reti; PC = 0x0004 reti; PC = 0x0006 reti; PC = 0x0008 reti; PC = 0x000A … reti; PC = 0x001E jmp overflow_handler: PC = 0x0020 reti: PC = 0x0022 … reti; PC = 0x0030 reti; Komputer = 0x0032
Tak więc, jeśli dane przerwanie wystąpi, po prostu "reti", co oznacza "powrót z przerwania" i nic więcej się nie dzieje. Ale jeśli nigdy nie „Włączymy” tych różnych przerwań, to nie będą one używane i możemy umieścić kod programu w tych miejscach. W naszym obecnym programie "blink.asm" zamierzamy tylko włączyć przerwanie przepełnienia timera0 (i oczywiście przerwanie resetowania, które jest zawsze włączone), więc nie będziemy zawracać sobie głowy innymi.
Jak w takim razie "włączyć" przerwanie przepełnienia timera0? … to temat naszego kolejnego kroku w tym samouczku.
Krok 5: Zegar/licznik 0
Spójrz na powyższy obrazek. Jest to proces decyzyjny „PC”, kiedy jakiś wpływ z zewnątrz „przerywa” przepływ naszego programu. Pierwszą rzeczą, jaką robi, gdy otrzymuje sygnał z zewnątrz, że wystąpiło przerwanie, jest sprawdzanie, czy ustawiliśmy bit „włączenia przerwań” dla tego typu przerwania. Jeśli nie, to po prostu kontynuuje wykonywanie naszego następnego wiersza kodu. Jeśli ustawiliśmy ten konkretny bit włączania przerwań (tak, że w tym miejscu bitu jest 1 zamiast 0), sprawdzi, czy włączyliśmy "przerwania globalne", jeśli nie, ponownie przejdzie do następnej linii kodu i kontynuuj. Jeśli włączyliśmy również globalne przerwania, to przejdzie do lokalizacji pamięci programowej tego typu przerwania (jak pokazano w Tabeli 12-6) i wykona dowolne polecenie, które tam umieściliśmy. Zobaczmy więc, jak zaimplementowaliśmy to wszystko w naszym kodzie.
Sekcja naszego kodu z etykietą Reset zaczyna się od następujących dwóch wierszy:
Resetowanie:
ldi temp, 0b00000101 z TCCR0B, temp
Jak już wiemy, wczytuje to do temp (tj. R16) numer następujący bezpośrednio po nim, czyli 0b00000101. Następnie wpisuje ten numer do rejestru o nazwie TCCR0B za pomocą polecenia "out". Co to za rejestr? Cóż, przejdźmy do strony 614 arkusza danych. To jest w środku tabeli podsumowującej wszystkie rejestry. Pod adresem 0x25 znajdziesz TCCR0B. (Teraz wiesz, skąd wzięła się linia „out 0x25, r16” w mojej niekomentowanej wersji kodu). Widzimy po powyższym segmencie kodu, że ustawiliśmy bit zerowy i bit drugi, a resztę wyczyściliśmy. Patrząc na tabelę widać, że oznacza to, że ustawiliśmy CS00 i CS02. Przejdźmy teraz do rozdziału w arkuszu danych o nazwie „8-bitowy zegar/licznik 0 z PWM”. W szczególności przejdź do strony 107 tego rozdziału. Zobaczysz ten sam opis rejestru „Timer/Counter Control Register B” (TCCR0B), który właśnie widzieliśmy w tabeli podsumowania rejestrów (więc mogliśmy przyjść tutaj od razu, ale chciałem, abyś zobaczył, jak korzystać z tabel podsumowujących dla przyszłego odniesienia). Arkusz danych nadal podaje opis każdego z bitów w tym rejestrze i tego, co robią. Na razie pominiemy to wszystko i przejdziemy do tabeli 15-9. Ta tabela pokazuje „Opis bitu wyboru zegara”. Teraz spójrz w dół tej tabeli, aż znajdziesz linię, która odpowiada bitom, które właśnie ustawiliśmy w tym rejestrze. Linia mówi „clk/1024 (z preskalera)”. Oznacza to, że chcemy, aby Timer/Licznik0 (TCNT0) tykał z szybkością, która jest częstotliwością procesora podzieloną przez 1024. Ponieważ nasz mikrokontroler jest zasilany przez oscylator kwarcowy 16 MHz, oznacza to, że szybkość, z jaką nasz procesor wykonuje instrukcje, jest 16 milionów instrukcji na sekundę. Czyli szybkość, z jaką nasz licznik TCNT0 będzie tykał, wynosi wtedy 16 milionów/1024 = 15625 razy na sekundę (spróbuj z różnymi bitami wyboru zegara i zobacz, co się stanie - pamiętasz naszą filozofię?). Zachowajmy numer 15625 na później i przejdźmy do następnych dwóch linijek kodu:
temperatura ldi, 0b000000001
sts TIMSK0, temp
Ustawia to 0 bit rejestru o nazwie TIMSK0 i czyści całą resztę. Jeśli spojrzysz na stronę 109 w arkuszu danych, zobaczysz, że TIMSK0 oznacza „Timer/Counter Interrupt Mask Register 0”, a nasz kod ustawił 0 bit, który nazywa się TOIE0, co oznacza „Timer/Counter0 Overflow Interrupt Enable” … Tam! Teraz widzisz, o co w tym wszystkim chodzi. Mamy teraz „ustawiony bit włączania przerwań”, tak jak chcieliśmy, od pierwszej decyzji na naszym obrazku u góry. Więc teraz wszystko, co musimy zrobić, to włączyć "przerwania globalne", a nasz program będzie w stanie odpowiedzieć na tego typu przerwania. Niedługo włączymy globalne przerwania, ale zanim to zrobimy, być może coś cię zdezorientowało… dlaczego do cholery użyłem polecenia „sts” do skopiowania do rejestru TIMSK0 zamiast zwykłego „out”?
Ilekroć mnie widzisz, użyj instrukcji, której nie widziałeś wcześniej, pierwszą rzeczą, którą powinieneś zrobić, jest przejście do strony 616 w arkuszu danych. To jest „Podsumowanie zestawu instrukcji”. Teraz znajdź instrukcję "STS", której użyłem. Mówi, że pobiera liczbę z rejestru R (użyliśmy R16) i lokalizację k „Zapisz bezpośrednio do SRAM” (w naszym przypadku podaną przez TIMSK0). Dlaczego więc musieliśmy używać "sts", który zajmuje 2 cykle zegara (patrz ostatnia kolumna w tabeli), aby zapisać w TIMSK0, a potrzebowaliśmy tylko "out", który zajmuje tylko jeden cykl zegara, aby zapisać go wcześniej w TCCR0B? Aby odpowiedzieć na to pytanie, musimy wrócić do naszej tabeli podsumowania rejestrów na stronie 614. Widzisz, że rejestr TCCR0B jest pod adresem 0x25, ale także pod adresem (0x45), prawda? Oznacza to, że jest to rejestr w SRAM, ale jest to również pewien typ rejestru zwany „portem” (lub rejestrem we/wy). Jeśli spojrzysz na tabelę podsumowania instrukcji obok komendy "out", zobaczysz, że pobiera ona wartości z "rejestrów roboczych" takich jak R16 i wysyła je do PORT. Możemy więc użyć "out" podczas zapisywania do TCCR0B i zaoszczędzić sobie cyklu zegara. Ale teraz spójrz na TIMSK0 w tabeli rejestrów. Widzisz, że ma adres 0x6e. Jest to poza zakresem portów (które są tylko pierwszymi lokalizacjami 0x3F SRAM), więc musisz wrócić do używania polecenia sts i wykonania dwóch cykli zegara procesora. Prosimy o zapoznanie się z uwagą 4 na końcu tabeli podsumowania instrukcji na stronie 615. Zauważ również, że wszystkie nasze porty wejściowe i wyjściowe, takie jak PORTD, znajdują się na dole tabeli. Na przykład PD4 to bit 4 pod adresem 0x0b (teraz widzisz, skąd pochodzą wszystkie rzeczy 0x0b w moim niezakomentowanym kodzie!).. ok, szybkie pytanie: czy zmieniłeś "sts" na "out" i zobacz co dzieje się? Zapamiętaj naszą filozofię! Złam to! nie tylko wierz mi na słowo.
Dobra, zanim przejdziemy dalej, przejdź na stronę 19 w arkuszu danych na minutę. Zobaczysz obraz pamięci danych (SRAM). Pierwsze 32 rejestry w SRAM (od 0x0000 do 0x001F) to „rejestry robocze ogólnego przeznaczenia” od R0 do R31, których używamy cały czas jako zmienne w naszym kodzie. Kolejne 64 rejestry to porty I/O do 0x005f (tzn. te, o których mówiliśmy, które mają obok siebie te adresy bez nawiasów w tabeli rejestrów, do których możemy użyć polecenia „out” zamiast „sts”) Na koniec następna sekcja SRAM zawiera wszystkie inne rejestry w tablicy podsumowań aż do adresu 0x00FF, a na koniec reszta to wewnętrzna SRAM. Teraz szybko przejdźmy na chwilę do strony 12. Widzisz tam tabelę "rejestrów roboczych ogólnego przeznaczenia", których zawsze używamy jako naszych zmiennych. Widzisz grubą linię między numerami od R0 do R15, a następnie od R16 do R31? Ta linia jest powodem, dla którego zawsze używamy R16 jako najmniejszego i zajmę się nim nieco bardziej w następnym samouczku, w którym będziemy również potrzebować trzech 16-bitowych rejestrów adresów pośrednich, X, Y i Z. Nie będę wejdź w to jeszcze, ponieważ nie potrzebujemy tego teraz i jesteśmy już wystarczająco grzęzną w tym miejscu.
Odwróć jedną stronę do strony 11 arkusza danych. Zobaczysz schemat rejestru SREG w prawym górnym rogu? Widzisz, że bit 7 tego rejestru nazywa się „I”. Teraz zejdź na dół strony i przeczytaj opis Bitu 7…. tak! Jest to bit Global Interrupt Enable. To jest to, co musimy ustawić, aby przejść przez drugą decyzję w naszym powyższym diagramie i zezwolić na przerwania timera/counter overflow w naszym programie. Tak więc następna linijka naszego programu powinna brzmieć:
sbi SREG, I
który ustawia bit o nazwie „I” w rejestrze SREG. Jednak zamiast tego użyliśmy instrukcji
sei
zamiast. Ten bit jest tak często ustawiany w programach, że po prostu zrobili to w prostszy sposób.
W porządku! Teraz mamy gotowe przerwania przepełnienia, aby nasz "jmp overflow_handler" był wykonywany za każdym razem, gdy wystąpi.
Zanim przejdziemy dalej, rzuć okiem na rejestr SREG (Rejestr Statusu), bo to bardzo ważne. Przeczytaj, co reprezentuje każda z flag. W szczególności wiele instrukcji, których używamy, będzie ustawiać i sprawdzać te flagi przez cały czas. Na przykład później będziemy używać polecenia „CPI”, co oznacza „porównaj natychmiast”. Spójrz na tabelę podsumowania instrukcji dla tej instrukcji i zauważ, ile flag ustawia ona w kolumnie „flagi”. To wszystko są flagi w SREG i nasz kod będzie je ustawiał i sprawdzał na bieżąco. Wkrótce zobaczysz przykłady. Wreszcie ostatni fragment tej sekcji kodu to:
klr temp
out TCNT0, temp sbi DDRD, 4
Ostatnia linijka tutaj jest dość oczywista. Po prostu ustawia czwarty bit rejestru kierunku danych dla PortD, powodując, że PD4 jest WYJŚCIEM.
Pierwsza ustawia zmienną temp na zero, a następnie kopiuje ją do rejestru TCNT0. TCNT0 to nasz Timer/Counter0. To ustawia go na zero. Gdy tylko komputer wykona tę linię, timer0 zacznie od zera i będzie liczył 15625 razy na sekundę. Problem polega na tym: TCNT0 jest rejestrem „8-bitowym”, prawda? Więc jaka jest największa liczba, jaką może pomieścić rejestr 8-bitowy? Cóż, 0b11111111 to jest to. To jest liczba 0xFF. Czyli 255. Więc widzisz, co się dzieje? Timer przyspiesza, zwiększając 15625 razy na sekundę i za każdym razem, gdy osiągnie 255, „przepełnia się” i ponownie wraca do 0. W tym samym czasie, gdy wraca do zera, wysyła sygnał przerwania Timer Overflow Interrupt. Komputer to rozumie i już wiesz, co robi, prawda? Tak. Udaje się do lokalizacji 0x0020 w pamięci programu i wykonuje znalezioną tam instrukcję.
Świetny! Jeśli nadal jesteś ze mną, jesteś niestrudzonym superbohaterem! Idźmy dalej…
Krok 6: Obsługa przepełnienia
Załóżmy więc, że rejestr timera/counter0 właśnie się przepełnił. Teraz wiemy, że program otrzymuje sygnał przerwania i wykonuje 0x0020, który mówi Licznikowi Programu, aby przeskoczył do etykiety "overflow_handler", poniżej znajduje się kod, który napisaliśmy po tej etykiecie:
overflow_handler:
inc przelewy cpi przelewy, 61 brne PC+2 clr przelewy reti
Pierwszą rzeczą, jaką robi, jest inkrementacja zmiennej „przepełnienia” (tak nazywamy rejestr roboczy ogólnego przeznaczenia R17), a następnie „porównuje” zawartość przepełnień z liczbą 61. Sposób działania instrukcji cpi polega na tym, że po prostu odejmuje dwie liczby, a jeśli wynik wynosi zero, ustawia flagę Z w rejestrze SREG (mówiłem ci, że będziemy widzieć ten rejestr cały czas). Jeśli te dwie liczby są równe, to flaga Z będzie 1, jeśli dwie liczby nie są równe, to będzie to 0.
Następna linia mówi "brne PC+2", co oznacza "gałąź, jeśli nie jest równa". Zasadniczo sprawdza flagę Z w SREG i jeśli NIE jest to jeden (tj. dwie liczby nie są równe, gdyby były równe, ustawiona byłaby flaga zero) PC rozgałęzia się do PC+2, co oznacza, że pomija następny wiersz i przechodzi prosto do "reti", które powraca z przerwania do dowolnego miejsca w kodzie, kiedy nadeszło przerwanie. Jeśli instrukcja brne znalazłaby 1 w bicie flagi zero, nie rozgałęziłaby się, a zamiast tego po prostu przeszłaby do następnej linii, która przepełniłaby clr, resetując ją do 0.
Jaki jest wynik netto tego wszystkiego?
Widzimy, że za każdym razem, gdy występuje przepełnienie timera, ten program obsługi zwiększa wartość „przepełnień” o jeden. Tak więc zmienna „przepełnienia” zlicza liczbę przepełnień w momencie ich wystąpienia. Za każdym razem, gdy liczba osiągnie 61, resetujemy ją do zera.
Dlaczego na świecie mielibyśmy to zrobić?
Zobaczmy. Przypomnij sobie, że nasze taktowanie dla naszego procesora wynosi 16 MHz i "przeskalowaliśmy" je za pomocą TCCR0B, aby zegar liczył tylko z szybkością 15625 zliczeń na sekundę, prawda? I za każdym razem, gdy licznik osiągnie liczbę 255, przepełnia się. Oznacza to, że przelewa się 15625/256 = 61,04 razy na sekundę. Liczbę przepełnień śledzimy za pomocą naszej zmiennej „przepełnienia” i porównujemy tę liczbę z 61. Widzimy więc, że „przepełnienia” będą równe 61 raz na sekundę! Więc nasz handler zresetuje "przepełnienia" do zera raz na sekundę. Więc gdybyśmy mieli po prostu monitorować „przepełnienia” zmiennej i odnotowywać za każdym razem, gdy resetuje się ona do zera, liczylibyśmy sekunda po sekundzie w czasie rzeczywistym (pamiętaj, że w następnym samouczku pokażemy, jak uzyskać dokładniejsze opóźnienie w milisekundach w taki sam sposób, w jaki działa procedura "opóźnienia" Arduino).
Teraz "obsłużyliśmy" przerwania przepełnienia timera. Upewnij się, że rozumiesz, jak to działa, a następnie przejdź do następnego kroku, w którym wykorzystujemy ten fakt.
Krok 7: Opóźnienie
Teraz, gdy widzieliśmy, że nasza procedura obsługi przerwań przepełnienia timera „overflow_handler” ustawia zmienną „overflows” na zero raz na sekundę, możemy wykorzystać ten fakt do zaprojektowania podprocedury „opóźnienia”.
Spójrz na poniższy kod spod naszego opóźnienia: label
opóźnienie:
przepełnienia clr sec_count: cpi overflows, 30 brne sec_count ret
Będziemy wywoływać ten podprogram za każdym razem, gdy potrzebujemy opóźnienia w naszym programie. Działa to tak, że najpierw ustawia zmienną "przepełnienia" na zero. Następnie wchodzi do obszaru oznaczonego "sec_count" i porównuje przepełnienia z 30, jeśli nie są równe, rozgałęzia się z powrotem do etykiety sec_count i porównuje znowu i znowu, itd., aż w końcu są równe (pamiętaj, że przez cały czas to się dzieje na naszym zegarze obsługi przerwań kontynuuje zwiększanie przepełnień zmiennych, więc zmienia się za każdym razem, gdy tutaj przejdziemy. Kiedy przepełnienia w końcu wyniosą 30, wychodzi z pętli i wraca do miejsca, w którym nazwaliśmy opóźnienie: z. Wynikiem netto jest opóźnienie 1/2 sekundy
Ćwiczenie 2: Zmień procedurę overflow_handler na następującą:
overflow_handler:
inc przelewy reti
i uruchom program. Czy coś się zmieniło? Dlaczego lub dlaczego nie?
Krok 8: Mrugnij
Na koniec spójrzmy na procedurę migania:
migać:
sbi PORTD, 4 opóźnienia wywołania cbi PORTD, 4 opóźnienia wywołania rjmp migają
Najpierw włączamy PD4, potem wywołujemy nasz podprogram opóźnienia. Używamy rcall, więc kiedy komputer dotrze do instrukcji "ret", wróci do linii następującej po wywołaniu rcall. Następnie procedura opóźnienia opóźnia o 30 zliczeń w zmiennej przepełnienia, jak widzieliśmy, i jest to prawie dokładnie 1/2 sekundy, następnie wyłączamy PD4, opóźniamy kolejne 1/2 sekundy, a następnie wracamy do początku.
Wynik netto to migająca dioda LED!
Myślę, że zgodzisz się teraz, że "blink" prawdopodobnie nie jest najlepszym programem "hello world" w języku asemblerowym.
Ćwiczenie 3: Zmień różne parametry w programie tak, aby dioda LED migała z różną częstotliwością, na przykład sekundę lub 4 razy na sekundę, itd. Ćwiczenie 4: Zmień to tak, aby dioda LED była włączana i wyłączana przez różne okresy czasu. Na przykład włącz na 1/4 sekundy, a potem wyłącz na 2 sekundy lub coś w tym stylu. Ćwiczenie 5: Zmień wybór bitów zegara TCCR0B na 100, a następnie kontynuuj chodzenie w górę tabeli. W którym momencie staje się nie do odróżnienia od naszego programu "hello.asm" z samouczka 1? Ćwiczenie 6 (opcjonalnie): Jeśli masz inny oscylator kwarcowy, na przykład 4 MHz, 13,5 MHz lub cokolwiek innego, wymień oscylator 16 MHz na płytce prototypowej dla nowego i zobacz, jak wpływa to na częstotliwość migania diody LED. Powinieneś teraz być w stanie przejść przez dokładne obliczenia i dokładnie przewidzieć, jak wpłynie to na kurs.
Krok 9: Wniosek
Dla tych zagorzałych, którzy dotarli tak daleko, Gratulacje!
Zdaję sobie sprawę, że jest to dość trudne, gdy więcej czytasz i patrzysz w górę niż okablowanie i eksperymentowanie, ale mam nadzieję, że nauczyłeś się następujących ważnych rzeczy:
- Jak działa pamięć programu
- Jak działa SRAM
- Jak wyszukiwać rejestry
- Jak wyszukiwać instrukcje i wiedzieć, co robią
- Jak zaimplementować przerwania
- Jak CP wykonuje kod, jak działa SREG i co dzieje się podczas przerwań?
- Jak robić pętle, skoki i podskakiwać w kodzie?
- Jak ważne jest przeczytanie arkusza danych!
- Jak już wiesz, jak to wszystko zrobić dla mikrokontrolera Atmega328p, będzie to względny spacer, aby nauczyć się nowych kontrolerów, którymi jesteś zainteresowany.
- Jak zmienić czas procesora na czas rzeczywisty i wykorzystać go w procedurach opóźniających.
Teraz, gdy mamy dużo teorii, jesteśmy w stanie pisać lepszy kod i kontrolować bardziej skomplikowane rzeczy. Więc w następnym samouczku właśnie to zrobimy. Zbudujemy bardziej skomplikowany, ciekawszy tor i będziemy nim sterować w zabawny sposób.
Ćwiczenie 7: „Złam” kod na różne sposoby i zobacz, co się stanie! Naukowa ciekawość kochanie! Ktoś inny może dobrze umyć naczynia? Ćwiczenie 8: Złóż kod za pomocą opcji "-l", aby wygenerować plik z listą. Tj. "avra -l blink.lst blink.asm" i spójrz na plik z listą. Extra Credit: Niezakomentowany kod, który podałem na początku, i kod z komentarzem, który omówimy później, różnią się! Jest jedna linia kodu, która jest inna. Czy możesz to znaleźć? Dlaczego ta różnica nie ma znaczenia?
Mam nadzieję, że dobrze się bawiłeś! Do zobaczenia następnym razem…
Zalecana:
Samouczek asemblera AVR 2: 4 kroki
AVR Assembler Tutorial 2: Ten samouczek jest kontynuacją „AVR Assembler Tutorial 1”; Jeśli nie przeszedłeś przez Samouczek 1, powinieneś przerwać teraz i zrobić to jako pierwszy. W tym samouczku będziemy kontynuować naszą naukę programowania w asemblerze atmega328p u
Samouczek asemblera AVR 1: 5 kroków
AVR Assembler Tutorial 1: Zdecydowałem się napisać serię samouczków na temat pisania programów w asemblerze dla Atmega328p, który jest mikrokontrolerem używanym w Arduino. Jeśli ludzie pozostaną zainteresowani, będę nadal wystawiał jedną na tydzień, aż skończą mi się
Samouczek asemblera AVR 7: 12 kroków
Samouczek 7 AVR Assembler: Witamy w samouczku 7! Dzisiaj najpierw pokażemy, jak oczyścić klawiaturę, a następnie pokażemy, jak używać portów wejścia analogowego do komunikacji z klawiaturą. Zrobimy to za pomocą przerwań i pojedynczego przewodu jako Wejście. Podłączymy klawiaturę, aby
Samouczek asemblera AVR 9: 7 kroków
Samouczek AVR Assembler 9: Witamy w samouczku 9. Dzisiaj pokażemy, jak sterować zarówno wyświetlaczem 7-segmentowym, jak i wyświetlaczem 4-cyfrowym za pomocą naszego kodu języka asemblera ATmega328P i AVR. W trakcie tego będziemy musieli zapoznać się z sposobami korzystania ze stosu
Samouczek asemblera AVR 11: 5 kroków
Samouczek AVR Assembler 11: Witamy w samouczku 11! W tym krótkim samouczku w końcu zbudujemy pierwszą część naszego końcowego projektu. Pierwszą rzeczą, którą powinieneś zrobić, to przejść do ostatniego kroku tego samouczka i obejrzeć film. Więc wróć tutaj. [pauza, podczas gdy ty