Innehållsförteckning:

AVR Assembler Tutorial 3: 9 Steg
AVR Assembler Tutorial 3: 9 Steg

Video: AVR Assembler Tutorial 3: 9 Steg

Video: AVR Assembler Tutorial 3: 9 Steg
Video: you can become a GIGACHAD assembly programmer in 10 minutes (try it RIGHT NOW) 2024, Juli
Anonim
AVR -monteringshandledning 3
AVR -monteringshandledning 3

Välkommen till självstudie nummer 3!

Innan vi sätter igång vill jag göra en filosofisk poäng. Var inte rädd för att experimentera med kretsarna och koden som vi bygger i dessa självstudier. Byt kablar runt, lägg till nya komponenter, ta ut komponenter, byt kodrader, lägg till nya rader, radera linjer och se vad som händer! Det är väldigt svårt att bryta något och vem bryr sig om du gör det? Ingenting vi använder, inklusive mikrokontrollern, är väldigt dyrt och det är alltid lärorikt att se hur saker kan misslyckas. Inte bara kommer du att ta reda på vad du inte ska göra nästa gång, men ännu viktigare, du kommer att veta varför du inte ska göra det. Om du är något som jag, när du var liten och fick en ny leksak var det inte så länge innan du hade den i bitar för att se vad som fick den att ticka rätt? Ibland hamnade leksaken på en irreparabelt skada men ingen stor grej. Att tillåta ett barn att utforska sin nyfikenhet även till trasiga leksaker är det som gör honom till en forskare eller ingenjör istället för en diskmaskin.

Idag kommer vi att koppla in en mycket enkel krets och sedan bli lite tunga i teorin. Förlåt för detta, men vi behöver verktygen! Jag lovar att vi kommer att kompensera för detta i självstudie 4 där vi kommer att göra en mer seriös kretsbyggnad och resultatet blir ganska coolt. Hur du behöver göra alla dessa självstudier är dock på ett mycket långsamt, kontemplativt sätt. Om du bara plöjer igenom, bygg kretsen, kopiera och klistra in koden och kör den sedan, det kommer säkert att fungera, men du lär dig ingenting. Du måste tänka på varje rad. Paus. Experimentera. Uppfinna. Om du gör det på det sättet kommer du i slutet av den femte handledningen att bygga coola saker och behöver inte mer handledning. Annars tittar du helt enkelt snarare än att lära och skapa.

I alla fall, nog med filosofi, låt oss komma igång!

I denna handledning behöver du:

  1. din prototypbräda
  2. en LED
  3. anslutningskablar
  4. ett motstånd runt 220 till 330 ohm
  5. Bruksanvisningen: www.atmel.com/images/atmel-0856-avr-instruction-se…
  6. Datablad: www.atmel.com/images/Atmel-8271-8-bit-AVR-Microco…
  7. en annan kristalloscillator (tillval)

Här är en länk till hela samlingen av självstudier:

Steg 1: Konstruera kretsen

Konstruera kretsen
Konstruera kretsen

Kretsen i denna handledning är extremt enkel. Vi kommer i huvudsak att skriva "blink" -programmet så allt vi behöver är följande.

Anslut en LED till PD4, sedan till ett 330 ohm motstånd, sedan till Ground. d.v.s.

PD4 - LED - R (330) - GND

och det är det!

Teorin kommer dock att bli tuff …

Steg 2: Varför behöver vi kommentarerna och filen M328Pdef.inc?

Jag tycker att vi bör börja med att visa varför inkluderingsfilen och kommentarerna är till hjälp. Ingen av dem är faktiskt nödvändiga och du kan skriva, montera och ladda upp kod på samma sätt utan dem och det kommer att fungera perfekt (även om du inte kan inkludera filen kan du få några klagomål från monteraren - men inga fel)

Här är koden vi ska skriva idag, förutom att jag har tagit bort kommentarerna och inkludera filen:

.enhet 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: sbi 0x0b, 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

ganska enkelt va? Haha. Om du monterade och laddade upp den här filen kommer du att få LED -lampan att blinka med en blinkning med 1 blinkning per sekund med blinkningen som varar 1/2 sekund och pausen mellan blinkningarna varar 1/2 sekund.

Att titta på denna kod är dock knappast upplysande. Om du skulle skriva kod som den här skulle du vilja ändra den eller återanvända den i framtiden skulle du ha svårt.

Så låt oss lägga in kommentarerna och inkludera filen igen så att vi kan förstå det.

Steg 3: Blink.asm

Här är koden som vi kommer att diskutera idag:

;************************************

; skriven av: 1o_o7; datum:; version: 1.0; filen sparas som: blink.asm; för AVR: atmega328p; klockfrekvens: 16MHz (tillval); *************************************; Programfunktion: ---------------------; räknar av sekunder genom att blinka en lysdiod;; PD4 - LED - R (330 ohm) - GND;; --------------------------------------.nolist. inkludera "./m328Pdef.inc".list; ===============; Deklarationer:.def temp = r16.def overflows = r17.org 0x0000; minne (PC) plats för återställningshanterare rjmp Återställ; jmp kostar 2 cpu -cykler och rjmp kostar bara 1; så om du inte behöver hoppa mer än 8k byte; du behöver bara rjmp. Vissa mikrokontroller därför endast; har rjmp och inte jmp.org 0x0020; minnesplats för Timer0 overflow handler rjmp overflow_handler; gå hit om ett timer0 -överflödsavbrott inträffar; ============ Återställ: ldi temp, 0b00000101 out TCCR0B, temp; ställ in klockväljaren Bits CS00, CS01, CS02 till 101; detta sätter Timer Counter0, TCNT0 i FCPU/1024 -läge; så det tickar vid CPU -frekvensen/1024 ldi temp, 0b00000001 st TIMSK0, temp; ställ in Timer Overflow Interrupt Enable (TOIE0) bit; av Timer Interrupt Mask Register (TIMSK0) sei; aktivera globala avbrott - motsvarande "sbi SREG, I" clr temp out TCNT0, temp; initiera timern/räknaren till 0 sbi DDRD, 4; ställ in PD4 till utmatning; ========================= Huvudprogrammet: blink: sbi PORTD, 4; slå på LED på PD4 rcall delay; fördröjning blir 1/2 sekund cbi PORTD, 4; stäng av LED på PD4 rcall delay; fördröjning blir 1/2 sekund rjmp blinkning; slinga tillbaka till startfördröjningen: clr svämmar över; ställ överflöden till 0 sec_count: cpi överflöden, 30; jämför antal överflöden och 30 brne sec_count; gren till tillbaka till sec_count om inte lika ret; om 30 överflöden har inträffat återgår till blinkande overflow_handler: inc överflöden; lägg till 1 till överflödesvariabeln cpi overflows, 61; jämför med 61 brne PC+2; Programräknare + 2 (hoppa över nästa rad) om inte lika clr -överflöden; om 61 överflöden inträffade återställ räknaren till noll reti; återvända från avbrott

Som ni ser är mina kommentarer lite kortare nu. När vi väl vet vad kommandona i instruktionssetet behöver vi inte förklara det i kommentarer. Vi behöver bara förklara vad som pågår ur programmets synvinkel.

Vi kommer att diskutera vad allt detta gör bit för bit, men låt oss först försöka få ett globalt perspektiv. Huvuddelen av programmet fungerar enligt följande.

Först satte vi bit 4 på PORTD med "sbi PORTD, 4" detta skickar en 1 till PD4 som sätter spänningen till 5V på den stiftet. Detta tänder lysdioden. Vi hoppar sedan till "fördröjning" -underutinen som räknar ut 1/2 sekund (vi kommer att förklara hur det gör detta senare). Vi återvänder sedan för att blinka och rensa bit 4 på PORTD som ställer in PD4 på 0V och stänger därför av lysdioden. Vi fördröjer sedan ytterligare en 1/2 sekund och hoppar sedan tillbaka till början av blinkningen igen med "rjmp blink".

Du bör köra den här koden och se att den gör vad den ska.

Och där har du det! Det är allt denna kod gör fysiskt. Den interna mekaniken för vad mikrokontrollern gör är lite mer involverad och det är därför vi gör den här självstudien. Så låt oss diskutera varje avsnitt i tur och ordning.

Steg 4:.org Assembler -direktiv

Vi vet redan vad.nolist,.list,.include och.def assembler -direktiven gör från våra tidigare självstudier, så låt oss först ta en titt på de fyra kodraderna som kommer efter det:

.org 0x0000

jmp Återställ.org 0x0020 jmp overflow_handler

. Org -satsen berättar för assembler var i "Programminne" att lägga nästa uttalande. När ditt program körs innehåller "Programräknaren" (förkortad som PC) adressen till den aktuella raden som körs. Så i det här fallet när datorn är på 0x0000 kommer den att se kommandot "jmp Reset" som finns i den minnesplatsen. Anledningen till att vi vill sätta jmp Reset på den platsen är att när programmet börjar eller chipet återställs börjar datorn köra kod på den här platsen. Så, som vi kan se, har vi precis sagt till den att omedelbart "hoppa" till avsnittet märkt "Återställ". Varför gjorde vi det? Det betyder att de två sista raderna ovan bara hoppas över! Varför?

Det är väl där saker blir intressanta. Du måste nu öppna en pdf -tittare med hela ATmega328p -databladet som jag pekade på på den första sidan i denna handledning (det är därför det är punkt 4 i avsnittet "du kommer att behöva"). Om din skärm är för liten, eller om du har alldeles för många fönster redan öppna (som är fallet med mig) kan du göra vad jag gör och lägga den på en Ereader eller din Android -telefon. Du kommer att använda den hela tiden om du planerar att skriva monteringskod. Det coola är att alla mikrokontroller är organiserade på mycket liknande sätt och så när du väl vänjer dig vid att läsa datablad och kodar från dem kommer du att tycka att det är nästan trivialt att göra samma sak för en annan mikrokontroller. Så vi lär oss faktiskt hur man använder alla mikrokontroller på ett sätt och inte bara atmega328p.

Okej, vänd till sidan 18 i databladet och ta en titt på Figur 8-2.

Så här konfigureras programminnet i mikrokontrollern. Du kan se att den börjar med adressen 0x0000 och är uppdelad i två sektioner; en applikationsblixtsektion och en startblixtsektion. Om du kort hänvisar till sidan 277 tabell 27-14 ser du att applikationsblixtsektionen tar platserna från 0x0000 till 0x37FF och startblixtsektionen tar upp de återstående platserna från 0x3800 till 0x3FFF.

Övning 1: Hur många platser finns det i programminnet? D.v.s. konvertera 3FFF till decimal och lägg till 1 eftersom vi börjar räkna till 0. Eftersom varje minnesplats är 16 bitar (eller 2 byte) bred, vad är det totala antalet byte i minne? Konvertera nu detta till kilobyte, kom ihåg att det finns 2^10 = 1024 byte i en kilobyte. Boot flash -delen går från 0x3800 till 0x37FF, hur många kilobyte är det här? Hur många kilobyte minne finns kvar för att vi ska kunna lagra vårt program? Med andra ord, hur stort kan vårt program vara? Slutligen, hur många kodrader kan vi ha?

Okej, nu när vi vet allt om organisationen av flash -programminnet, låt oss fortsätta med vår diskussion om.org -uttalanden. Vi ser att den första minnesplatsen 0x0000 innehåller vår instruktion att hoppa till vårt avsnitt som vi märkte Återställ. Nu ser vi vad uttalandet ".org 0x0020" gör. Det står att vi vill att instruktionen på nästa rad ska placeras på minnesplats 0x0020. Instruktionen vi har placerat där är ett hopp till ett avsnitt i vår kod som vi har märkt "overflow_handler" … nu varför skulle vi kräva att detta hopp placeras på minnesplats 0x0020? För att ta reda på det, vänder vi oss till sidan 65 i databladet och tittar på tabell 12-6.

Tabell 12-6 är en tabell med "Återställ och avbrottsvektorer" och den visar exakt vart datorn tar vägen när den får ett "avbrott". Om du till exempel tittar på vektornummer 1. Avbrottets "källa" är "ÅTERSTÄLL" som definieras som "Extern stift, återstart vid återstart, återställning av brunt ut och återställning av vakthundssystem", om någon av dessa saker händer med vår mikrokontroller, kommer datorn att börja köra vårt program på programminnets plats 0x0000. Vad sägs om vårt.org -direktiv då? Tja, vi placerade ett kommando på minnesplats 0x0020 och om du tittar ner i tabellen kommer du att se att om ett Timer/Counter0 -överflöd händer (kommer från TIMER0 OVF) kommer det att utföra det som finns på plats 0x0020. Så när det händer kommer datorn att hoppa till platsen som vi märkte "overflow_handler". Kul va? Du kommer att se om en minut varför vi gjorde det här, men låt oss först avsluta detta steg i handledningen med en sida.

Om vi vill göra vår kod mer snygg och städad bör vi verkligen ersätta de fyra raderna vi diskuterar med följande (se sidan 66):

.org 0x0000

rjmp Återställ; 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; PC = 0x0032

Så att om ett givet avbrott inträffar kommer det bara att "reti" vilket betyder "återvända från avbrott" och inget annat händer. Men om vi aldrig "Aktiverar" dessa olika avbrott, kommer de inte att användas och vi kan sätta programkod på dessa platser. I vårt nuvarande "blink.asm" -program kommer vi bara att aktivera timer0 -överflödsavbrottet (och naturligtvis återställningsavbrottet som alltid är aktiverat) och så kommer vi inte bry oss om de andra.

Hur "aktiverar" vi timer0 -överflödesavbrottet då? … det är föremålet för vårt nästa steg i denna handledning.

Steg 5: Timer/räknare 0

Timer/räknare 0
Timer/räknare 0

Ta en titt på bilden ovan. Detta är beslutsprocessen för "PC" när något yttre inflytande "avbryter" flödet av vårt program. Det första den gör när den får en signal utifrån att ett avbrott har inträffat är att den kontrollerar om vi har ställt in "interrupt enable" -biten för den typen av avbrott. Om vi inte har det, fortsätter det bara att köra vår nästa kodrad. Om vi har ställt in just den avbrottsaktiveringsbiten (så att det finns en 1 på den bitplatsen istället för en 0) kommer den sedan att kontrollera om vi har aktiverat "globala avbrott" eller inte, om det inte går igen till nästa rad kod och fortsätt. Om vi också har aktiverat globala avbrott kommer det att gå till programminnets plats för den typen av avbrott (som visas i tabell 12-6) och utföra det kommando vi har placerat där. Så låt oss se hur vi har implementerat allt detta i vår kod.

Avsnittet Återställ märkt i vår kod börjar med följande två rader:

Återställa:

ldi temp, 0b00000101 out TCCR0B, temp

Som vi redan vet laddar detta in temp (dvs. R16) numret omedelbart efter, vilket är 0b00000101. Sedan skriver det ut detta nummer till registret TCCR0B med kommandot "out". Vad är detta register? Låt oss gå vidare till sidan 614 i databladet. Detta är i mitten av en tabell som sammanfattar alla register. På adressen 0x25 hittar du TCCR0B. (Nu vet du var raden "out 0x25, r16" kom ifrån i min okommenterade version av koden). Vi ser med kodsegmentet ovan att vi har ställt in den 0: e biten och den andra biten och rensat resten. Genom att titta på tabellen kan du se att detta betyder att vi har ställt in CS00 och CS02. Låt oss nu gå vidare till kapitlet i databladet "8-bit Timer/Counter0 with PWM". Gå särskilt till sidan 107 i det kapitlet. Du kommer att se samma beskrivning av "Timer/Counter Control Register B" (TCCR0B) -registret som vi just såg i registeröversiktstabellen (så vi kunde ha kommit direkt hit, men jag ville att du skulle se hur du använder sammanfattningstabellerna för framtida referens). Databladet fortsätter att ge en beskrivning av var och en av bitarna i det registret och vad de gör. Vi hoppar över allt det nu och vänder sidan till tabell 15-9. Denna tabell visar "Beskrivning av klockvalsbit". Titta nu ner i tabellen tills du hittar raden som motsvarar bitarna som vi just ställt in i det registret. Raden säger "clk/1024 (från förkalkningsmedel)". Vad detta betyder är att vi vill att Timer/Counter0 (TCNT0) ska ticka med i en hastighet som är CPU -frekvensen dividerad med 1024. Eftersom vi har vår mikrokontroller matad av en 16MHz kristalloscillator betyder det att den hastighet som vår CPU kör instruktioner är 16 miljoner instruktioner per sekund. Så hastigheten som vår TCNT0 -räknare kommer att ticka är då 16 miljoner/1024 = 15625 gånger per sekund (prova med olika klockvalsbitar och se vad som händer - kom ihåg vår filosofi?). Låt oss behålla talet 15625 i bakhuvudet för senare och gå vidare till nästa två kodrader:

ldi temp, 0b00000001

m TIMSK0, temp

Detta ställer in den 0: e biten i ett register som heter TIMSK0 och rensar resten. Om du tittar på sidan 109 i databladet ser du att TIMSK0 står för "Timer/Counter Interrupt Mask Register 0" och vår kod har ställt in den 0: e biten som heter TOIE0 som står för "Timer/Counter0 Overflow Interrupt Enable" … Där! Nu ser du vad det här handlar om. Vi har nu "interrupt enable bit set" som vi ville från det första beslutet i vår bild högst upp. Så nu är allt vi behöver göra att aktivera "globala avbrott" och vårt program kommer att kunna svara på den här typen av avbrott. Vi kommer att aktivera globala avbrott inom kort, men innan vi gör det kan du ha blivit förvirrad av något.. varför fan använde jag kommandot "sts" för att kopiera till TIMSK0 -registret istället för det vanliga "ut"?

När du ser mig använder du en instruktion som du inte har sett innan det första du bör göra är att gå till sidan 616 i databladet. Detta är "Sammanfattning av instruktionsset". Hitta nu instruktionen "STS" som är den jag använde. Det står att det tar ett nummer från ett R -register (vi använde R16) och "Store direkt till SRAM" -plats k (i vårt fall ges av TIMSK0). Så varför var vi tvungna att använda "m" som tar 2 klockcykler (se sista kolumnen i tabellen) för att lagra i TIMSK0 och vi behövde bara "ut", som bara tar en klockcykel, för att lagra i TCCR0B tidigare? För att besvara denna fråga måste vi gå tillbaka till vår registeröversiktstabell på sidan 614. Du ser att TCCR0B -registret är på adressen 0x25 men också på (0x45) eller hur? Detta innebär att det är ett register i SRAM, men det är också en viss typ av register som kallas en "port" (eller i/o -register). Om du tittar på instruktionssammanfattningstabellen bredvid kommandot "out" ser du att det tar värden från "arbetsregistren" som R16 och skickar dem till en PORT. Så vi kan använda "out" när vi skriver till TCCR0B och spara oss en klockcykel. Men leta nu upp TIMSK0 i registertabellen. Du ser att den har adressen 0x6e. Detta ligger utanför portintervallet (som bara är de första 0x3F -platserna för SRAM) och så måste du falla tillbaka till att använda sts -kommandot och ta två CPU -klockcykler för att göra det. Läs not 4 i slutet av instruktionssammanfattningstabellen på sidan 615 just nu. Lägg också märke till att alla våra in- och utgångsportar, som PORTD, ligger längst ner i tabellen. Till exempel är PD4 bit 4 på adressen 0x0b (nu ser du var alla 0x0b-grejer kom ifrån i min okommenterade kod!).. okej, snabb fråga: ändrade du "m" till "ut" och såg vad händer? Kom ihåg vår filosofi! ha sönder den! ta inte bara mitt ord för saker.

Okej, innan vi går vidare, gå till sidan 19 i databladet i en minut. Du ser en bild av dataminnet (SRAM). De första 32 registren i SRAM (från 0x0000 till 0x001F) är de "allmänna arbetsregistren" R0 till R31 som vi hela tiden använder som variabler i vår kod. De nästa 64 registren är I/O-portarna upp till 0x005f (dvs de vi pratade om som har de orimade adresserna bredvid dem i registertabellen som vi kan använda kommandot "out" istället för "sts") Slutligen nästa avsnitt av SRAM innehåller alla andra register i sammanfattningstabellen upp till adressen 0x00FF, och slutligen är resten internt SRAM. Låt oss snabbt gå till sidan 12 för en sekund. Där ser du en tabell över de "allmänna arbetsregistren" som vi alltid använder som våra variabler. Ser du den tjocka linjen mellan siffrorna R0 till R15 och sedan R16 till R31? Den raden är därför vi alltid använder R16 som den minsta och jag kommer att gå in på det lite mer i nästa handledning där vi också kommer att behöva de tre 16-bitars indirekta adressregistren, X, Y och Z. Jag kommer inte gå in på det ännu men eftersom vi inte behöver det nu och vi håller på att fastna tillräckligt här.

Vänd tillbaka en sida till sidan 11 i databladet. Du ser ett diagram över SREG -registret högst upp till höger? Du ser att bit 7 i det registret kallas "jag". Gå nu ner på sidan och läs beskrivningen av Bit 7 …. Jippie! Det är Global Interrupt Enable -biten. Det är vad vi behöver ställa in för att passera det andra beslutet i vårt diagram ovan och tillåta avbrott i timer/räknare i vårt program. Så nästa rad i vårt program bör läsa:

sbi SREG, jag

som ställer in den bit som heter "I" i SREG -registret. Men istället för detta har vi använt instruktionen

sei

istället. Denna bit är inställd så ofta i program att de bara gjorde ett enklare sätt att göra det.

Okej! Nu har vi redo för överflödsavbrotten så att vår "jmp overflow_handler" kommer att köras när som helst.

Innan vi går vidare, ta en snabb titt på SREG -registret (statusregister) eftersom det är mycket viktigt. Läs vad var och en av flaggorna representerar. I synnerhet kommer många av de instruktioner som vi använder att ställa in och kontrollera dessa flaggor hela tiden. Till exempel kommer vi senare att använda kommandot "CPI" vilket betyder "jämför omedelbart". Ta en titt på instruktionssammanfattningstabellen för denna instruktion och märk hur många flaggor den sätter i kolumnen "flaggor". Dessa är alla flaggor i SREG och vår kod kommer att ställa in dem och kontrollera dem ständigt. Du kommer att se exempel inom kort. Slutligen är den sista biten av detta avsnitt av kod:

clr temp

ut TCNT0, temp sbi DDRD, 4

Den sista raden här är ganska uppenbar. Det ställer bara in den fjärde biten i datariktningsregistret för PortD vilket gör att PD4 är OUTPUT.

Den första ställer in variabeln temp till noll och kopierar sedan ut den till TCNT0 -registret. TCNT0 är vår Timer/Counter0. Detta sätter det till noll. Så snart datorn kör denna rad kommer timern 0 att börja på noll och räkna med en hastighet av 15625 gånger varje sekund. Problemet är detta: TCNT0 är väl ett "8-bitars" register? Så vad är det största antalet som ett 8-bitars register kan hålla? Tja 0b11111111 är det. Detta är talet 0xFF. Vilket är 255. Så du ser vad som händer? Timern zippar längs med att öka 15625 gånger i sekunden och varje gång den når 255 "överflödar" den och går tillbaka till 0 igen. Samtidigt som den går tillbaka till noll skickar den ut en timeröverflödesavbrottssignal. Datorn får det här och du vet vad det gör nu? Japp. Den går till Programminnets plats 0x0020 och kör instruktionen som den hittar där.

Bra! Om du fortfarande är med mig är du en outtröttlig superhjälte! Låt oss fortsätta…

Steg 6: Överflödeshanterare

Så låt oss anta att timer/counter0 -registret just har flutit över. Vi vet nu att programmet tar emot en avbrottssignal och kör 0x0020 som säger till programräknaren, PC: n att hoppa till etiketten "overflow_handler" följande är koden som vi skrev efter den etiketten:

overflow_handler:

inc överflöden cpi overflows, 61 brne PC+2 clr overflows reti

Det första den gör är att öka variabeln "överflöden" (vilket är vårt namn för allmänt fungerande register R17) sedan "jämför" innehållet i överflöden med siffran 61. Sättet som instruktionen cpi fungerar är att det helt enkelt subtraherar de två siffrorna och om resultatet är noll sätter det Z -flaggan i SREG -registret (jag sa att vi skulle se detta register hela tiden). Om de två talen är lika kommer Z -flaggan att vara 1, om de två talen inte är lika kommer det att vara ett 0.

Nästa rad säger "brne PC+2" vilket betyder "gren om inte lika". I huvudsak kontrollerar den Z -flaggan i SREG och om den INTE är en (dvs. de två siffrorna är inte lika, om de var lika skulle nollflaggan vara inställd) grenar datorn till PC+2, vilket betyder att den hoppar över nästa linje och går direkt till "reti" som återgår från avbrottet till vilken plats det än var i koden när avbrottet kom. Om brne -instruktionen hittade en 1 i nollflaggbiten skulle den inte förgrena sig och istället skulle den bara fortsätta till nästa rad som skulle clr -överflöden återställa den till 0.

Vad är nettoresultatet av allt detta?

Vi ser att varje gång det finns ett timeröverflöd ökar denna hanterare värdet på "överflöden" med en. Så variabeln "överflöden" räknar antalet överflöden när de inträffar. När siffran når 61 återställer vi den till noll.

Varför i helvete skulle vi göra det?

Låt oss se. Minns att vår klockhastighet för vår CPU är 16MHz och att vi "förskalade" den med TCCR0B så att timern bara räknas med en hastighet av 15625 räkningar per sekund? Och varje gång timern når 255 rinner den över. Så det betyder att det rinner över 15625/256 = 61,04 gånger per sekund. Vi håller koll på antalet överflöden med vår variabel "överflöden" och vi jämför det antalet med 61. Så vi ser att "överflöden" blir 61 en gång i sekunden! Så vår återställare kommer att återställa "överflöden" till noll en gång varannan sekund. Så om vi bara skulle övervaka variabeln "överflöden" och notera varje gång den återställs till noll skulle vi räkna sekund för sekund i realtid (Observera att i nästa handledning kommer vi att visa hur vi får en mer exakt fördröjning i millisekunder på samma sätt som Arduino "fördröjning" -rutinen fungerar).

Nu har vi "hanterat" timeröverflödesavbrotten. Se till att du förstår hur detta fungerar och gå vidare till nästa steg där vi använder detta faktum.

Steg 7: Fördröjning

Nu när vi har sett att vår timeröverflödesavbrottshanterare "overflow_handler" -rutin kommer att ställa in variabeln "överflöden" till noll en gång varje sekund kan vi använda detta faktum för att designa en "fördröjning" -rutin.

Ta en titt på följande kod från under vår fördröjning: etikett

dröjsmål:

clr överflöden sec_count: cpi overflows, 30 brne sec_count ret

Vi kommer att kalla detta subrutin varje gång vi behöver en fördröjning av vårt program. Så fungerar det genom att först sätta variabeln "överflöden" till noll. Sedan går det in i ett område märkt "sec_count" och jämför överflöden med 30, om de inte är lika, grenar det tillbaka till etiketten sec_count och jämför igen, och igen, etc. tills de äntligen är lika (kom ihåg att hela tiden detta går på vår timer avbryter hanteraren att öka variabelns överflöden och det förändras varje gång vi går runt här. När överflöden äntligen är lika med 30 kommer det ut ur slingan och återgår till vart vi än kallade fördröjning: från. Nettoresultatet är ett 1/2 sekund fördröjning

Övning 2: Ändra rutinen overflow_handler till följande:

overflow_handler:

inc överflödar reti

och kör programmet. Är det något annorlunda? Varför eller varför inte?

Steg 8: Blink

Slutligen ska vi titta på blinkrutinen:

blinka:

sbi PORTD, 4 rcall delay cbi PORTD, 4 rcall delay rjmp blink

Först slår vi på PD4, sedan ringer vi vår fördröjningssubrutin. Vi använder rcall så att när datorn kommer till ett "ret" -uttalande kommer den tillbaka till raden efter rcall. Sedan fördröjer fördröjningsrutinen för 30 räkningar i överflödesvariabeln som vi har sett och detta är nästan exakt 1/2 sekund, sedan stänger vi av PD4, fördröjer ytterligare 1/2 sekund och går sedan tillbaka till början igen.

Nettoresultatet är en blinkande lysdiod!

Jag tror att du nu kommer att hålla med om att "blinka" förmodligen inte är det bästa "hej världen" -programmet i samlingsspråk.

Övning 3: Ändra de olika parametrarna i programmet så att lysdioden blinkar i olika hastigheter som en sekund eller 4 gånger i sekunden etc. Övning 4: Ändra den så att lysdioden är på och av under olika lång tid. Till exempel på i 1/4 sekund och sedan av i 2 sekunder eller något liknande. Övning 5: Ändra TCCR0B -klockan, välj bitar till 100 och fortsätt sedan uppåt i tabellen. Vid vilken tidpunkt går det inte att skilja från vårt "hello.asm" -program från självstudie 1? Övning 6 (valfritt): Om du har en annan kristalloscillator, som en 4 MHz eller en 13,5 MHz eller vad som helst, byt ut din 16 MHz -oscillator på din brödbräda för den nya och se hur det påverkar blinkningshastigheten för lysdioden. Du bör nu kunna gå igenom den exakta beräkningen och förutse exakt hur det kommer att påverka hastigheten.

Steg 9: Slutsats

Till er som har kommit så här långt, grattis!

Jag inser att det är ganska svårt att slogga när du läser mer och tittar upp än du kopplar och experimenterar, men jag hoppas att du har lärt dig följande viktiga saker:

  1. Hur programminnet fungerar
  2. Hur SRAM fungerar
  3. Hur man letar upp registren
  4. Hur man letar upp instruktioner och vet vad de gör
  5. Hur man genomför avbrott
  6. Hur CP kör koden, hur SREG fungerar och vad som händer under avbrott
  7. Hur man gör slingor och hopp och studsar runt i koden
  8. Hur viktigt det är att läsa databladet!
  9. Hur när du väl vet hur du gör allt detta för Atmega328p mikrokontroller kommer det att bli en relativ tårtpromenad för att lära dig nya kontroller som du är intresserad av.
  10. Hur man ändrar CPU -tid till realtid och använder den i fördröjningsrutiner.

Nu när vi har mycket teori ur vägen kan vi skriva bättre kod och styra mer komplicerade saker. Så nästa handledning kommer vi att göra just det. Vi kommer att bygga en mer komplicerad, mer intressant krets och styra den på roliga sätt.

Övning 7: "Bryt" koden på olika sätt och se vad som händer! Vetenskaplig nyfikenhet älskling! Någon annan kan tvätta disken rätt? Övning 8: Sätt ihop koden med alternativet "-l" för att skapa en listfil. D.v.s. "avra -l blink.lst blink.asm" och ta en titt på listfilen. Extra kredit: Den okommenterade koden som jag gav i början och den kommenterade koden som vi diskuterar senare skiljer sig åt! Det finns en kodrad som är annorlunda. Kan du hitta den? Varför spelar inte den skillnaden någon roll?

Hoppas du hade roligt! Vi ses nästa gång …

Rekommenderad: