Innholdsfortegnelse:

AVR Assembler Tutorial 3: 9 Steps
AVR Assembler Tutorial 3: 9 Steps

Video: AVR Assembler Tutorial 3: 9 Steps

Video: AVR Assembler Tutorial 3: 9 Steps
Video: AVR Assembly Tutorial: Part 1 (Basic Commands) 2024, Juli
Anonim
AVR Assembler Tutorial 3
AVR Assembler Tutorial 3

Velkommen til opplæring nummer 3!

Før vi begynner, vil jeg komme med et filosofisk poeng. Ikke vær redd for å eksperimentere med kretsene og koden som vi konstruerer i disse opplæringsprogrammene. Bytt ledninger rundt, legg til nye komponenter, ta ut komponenter, endre kodelinjer, legg til nye linjer, slett linjer, og se hva som skjer! Det er veldig vanskelig å bryte noe, og hvis du gjør det, hvem bryr seg? Ingenting vi bruker, inkludert mikrokontrolleren, er veldig dyrt, og det er alltid lærerikt å se hvordan ting kan mislykkes. Ikke bare vil du finne ut hva du ikke skal gjøre neste gang, men enda viktigere er at du vet hvorfor du ikke skal gjøre det. Hvis du er noe som meg, da du var barn og du fikk et nytt leketøy, var det ikke lenge før du hadde den i stykker for å se hva som gjorde at den krysset riktig? Noen ganger endte leketøyet opp med uopprettelig skade, men ikke så farlig. Å la et barn utforske sin nysgjerrighet selv til poenget med ødelagte leker er det som gjør ham til en forsker eller ingeniør i stedet for en oppvaskmaskin.

I dag skal vi koble til en veldig enkel krets og deretter bli litt tung i teorien. Beklager dette, men vi trenger verktøyene! Jeg lover at vi skal ta igjen dette i opplæring 4, hvor vi skal gjøre en mer seriøs kretsbygging, og resultatet blir ganske kult. Imidlertid er måten du trenger å gjøre alle disse opplæringsprogrammene på en veldig sakte, kontemplativ måte. Hvis du bare pløyer igjennom, bygger du kretsen, kopierer og limer inn koden, og kjører den da, det fungerer sikkert, men du vil ikke lære noe. Du må tenke på hver linje. Pause. Eksperiment. Finne opp. Hvis du gjør det på den måten, vil du ved slutten av den femte opplæringen begynne å bygge kule ting og ikke trenger mer veiledning. Ellers ser du ganske enkelt fremfor å lære og skape.

Uansett, nok filosofi, la oss komme i gang!

I denne opplæringen trenger du:

  1. prototypebrettet ditt
  2. en LED
  3. tilkobling av ledninger
  4. en motstand rundt 220 til 330 ohm
  5. Instruksjonshåndboken: www.atmel.com/images/atmel-0856-avr-instruction-se…
  6. Dataarket: www.atmel.com/images/Atmel-8271-8-bit-AVR-Microco…
  7. en annen krystalloscillator (valgfritt)

Her er en lenke til den komplette samlingen av opplæringsprogrammer:

Trinn 1: Konstruere kretsen

Konstruere kretsen
Konstruere kretsen

Kretsen i denne opplæringen er ekstremt enkel. Vi skal egentlig skrive "blink" -programmet, så alt vi trenger er følgende.

Koble en LED til PD4, deretter til en 330 ohm motstand, deretter til bakken. dvs.

PD4 - LED - R (330) - GND

og det er det!

Teorien kommer imidlertid til å bli tøff …

Trinn 2: Hvorfor trenger vi kommentarene og filen M328Pdef.inc?

Jeg tror vi bør begynne med å vise hvorfor inkluderingsfilen og kommentarene er nyttige. Ingen av dem er faktisk nødvendige, og du kan skrive, montere og laste opp kode på samme måte uten dem, og det vil fungere helt greit (selv om du ikke kan inkludere filen, kan du få noen klager fra samleren - men ingen feil)

Her er koden vi skal skrive i dag, bortsett fra at jeg har fjernet kommentarene og inkluderingsfilen:

.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

ganske enkelt ikke sant? Ha ha. Hvis du har samlet og lastet opp denne filen, får du LED -lampen til å blinke med en hastighet på 1 blink i sekundet, med blinkingen i 1/2 sekund og pausen mellom blinkene i 1/2 sekund.

Å se på denne koden er imidlertid neppe opplysende. Hvis du skulle skrive kode som denne, og du ville endre den eller bruke den på nytt i fremtiden, ville du ha det vanskelig.

Så la oss legge inn kommentarene og inkludere filen tilbake, slik at vi kan forstå det litt.

Trinn 3: Blink.asm

Her er koden vi vil diskutere i dag:

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

; skrevet av: 1o_o7; Dato:; versjon: 1.0; filen lagret som: blink.asm; for AVR: atmega328p; klokkefrekvens: 16MHz (valgfritt); ************************************; Programfunksjon: ---------------------; teller sekunder ved å blinke en LED;; PD4 - LED - R (330 ohm) - GND;; --------------------------------------.nolist. inkludere "./m328Pdef.inc".list; ===============; Deklarasjoner:.def temp = r16.def overflows = r17.org 0x0000; minne (PC) plassering av reset handler rjmp Reset; jmp koster 2 cpu -sykluser og rjmp koster bare 1; så med mindre du trenger å hoppe mer enn 8k byte; du trenger bare rjmp. Noen mikrokontrollere derfor bare; har rjmp og ikke jmp.org 0x0020; minne plassering av Timer0 overflow handler rjmp overflow_handler; gå hit hvis det oppstår et timer0 overløp avbrudd; ============ Tilbakestill: ldi temp, 0b00000101 out TCCR0B, temp; sett klokkevelgerbitene CS00, CS01, CS02 til 101; dette setter Timer Counter0, TCNT0 i FCPU/1024 -modus; så det tikker ved CPU -frekvensen/1024 ldi temp, 0b00000001 st TIMSK0, temp; angi Timer Overflow Interrupt Enable (TOIE0) bit; av Timer Interrupt Mask Register (TIMSK0) sei; muliggjøre globale avbrudd - tilsvarende "sbi SREG, I" clr temp out TCNT0, temp; initialiser Timer/Counter til 0 sbi DDRD, 4; sett PD4 til output; ========================= Programmets hoveddel: blink: sbi PORTD, 4; slå på LED på PD4 rcall delay; forsinkelse vil være 1/2 sekund cbi PORTD, 4; slå av LED på PD4 rcall delay; forsinkelse vil være 1/2 sekund rjmp blink; sløyfe tilbake til startforsinkelsen: clr flyter over; sett overløp til 0 sec_count: cpi overløper, 30; sammenligne antall overløp og 30 brne sec_count; gren til tilbake til sec_count hvis ikke lik ret; hvis det har oppstått 30 overløp, blinker overflow_handler: inc overflows; legg til 1 til overløpsvariabelen cpi overflows, 61; sammenligne med 61 brne PC+2; Programteller + 2 (hopp over neste linje) hvis ikke like clr -overløp; tilbakestill telleren til null reti hvis 61 overløp oppstod; tilbake fra avbrudd

Som du kan se, er kommentarene mine litt mer korte nå. Når vi vet hva kommandoene i instruksjonssettet trenger, trenger vi ikke å forklare det i kommentarer. Vi trenger bare å forklare hva som skjer ut fra programmets synspunkt.

Vi vil diskutere hva alt dette gjør stykke for stykke, men la oss først prøve å få et globalt perspektiv. Programmets hoveddel fungerer som følger.

Først satte vi bit 4 av PORTD med "sbi PORTD, 4", dette sender en 1 til PD4 som setter spenningen til 5V på den pinnen. Dette vil slå på LED -en. Vi hopper deretter til "forsinkelsen" -rutinen som teller ut 1/2 sekund (vi vil forklare hvordan den gjør dette senere). Vi går deretter tilbake for å blinke og fjerne bit 4 på PORTD som setter PD4 til 0V og dermed slår av LED -en. Vi forsinker deretter ytterligere 1/2 sekund, og hopper deretter tilbake til begynnelsen av blink igjen med "rjmp blink".

Du bør kjøre denne koden og se at den gjør det den skal.

Og der har du det! Det er alt denne koden gjør fysisk. Den interne mekanikken til hva mikrokontrolleren gjør er litt mer involvert, og det er derfor vi gjør denne opplæringen. Så la oss diskutere hver seksjon etter tur.

Trinn 4:.org Assembler -direktiver

Vi vet allerede hva.nolist,.list,.include og.def assembler direktiver gjør fra våre tidligere opplæringsprogrammer, så la oss først se på de fire kodelinjene som kommer etter det:

.org 0x0000

jmp Tilbakestill.org 0x0020 jmp overflow_handler

. Org -setningen forteller samleren hvor "Programminne" skal sette den neste setningen. Etter hvert som programmet ditt kjøres, inneholder "Programtelleren" (forkortet som PC) adressen til den gjeldende linjen som utføres. Så i dette tilfellet når PC -en er på 0x0000 vil den se kommandoen "jmp Reset" som befinner seg på det minnestedet. Grunnen til at vi vil sette jmp Reset på det stedet er fordi når programmet starter, eller brikken blir tilbakestilt, begynner PC -en å utføre kode på dette stedet. Så, som vi kan se, har vi nettopp fortalt det å umiddelbart "hoppe" til delen merket "Tilbakestill". Hvorfor gjorde vi det? Det betyr at de to siste linjene ovenfor bare blir hoppet over! Hvorfor?

Vel, det er der ting blir interessante. Du må nå åpne en pdf -visning med hele ATmega328p -databladet som jeg pekte på på den første siden i denne opplæringen (det er derfor det er element 4 i delen "du trenger"). Hvis skjermen din er for liten, eller du har altfor mange vinduer åpne allerede (som det er tilfellet med meg), kan du gjøre det jeg gjør og legge den på en Ereader eller din Android -telefon. Du kommer til å bruke den hele tiden hvis du planlegger å skrive monteringskode. Det som er kult er at alle mikrokontrollere er organisert på veldig like måter, og så snart du blir vant til å lese datablad og koder fra dem, vil du synes det er nesten trivielt å gjøre det samme for en annen mikrokontroller. Så vi lærer faktisk hvordan vi bruker alle mikrokontrollere på en måte og ikke bare atmega328p.

Ok, gå til side 18 i databladet og ta en titt på figur 8-2.

Slik er programminnet i mikrokontrolleren konfigurert. Du kan se at den starter med adressen 0x0000 og er delt inn i to seksjoner; en applikasjonsblitseksjon og en oppstartsblitseksjon. Hvis du refererer kort til side 277 tabell 27-14, vil du se at applikasjonsblitsdelen tar opp plasseringene fra 0x0000 til 0x37FF og boot flash-delen tar opp de resterende stedene fra 0x3800 til 0x3FFF.

Oppgave 1: Hvor mange steder er det i programminnet? Dvs. konverter 3FFF til desimal og legg til 1 siden vi begynner å telle til 0. Siden hvert minnested er 16 bits (eller 2 byte) bredt, hva er det totale antallet byte minne? Konverter dette nå til kilobyte, husk at det er 2^10 = 1024 byte i en kilobyte. Boot flash -delen går fra 0x3800 til 0x37FF, hvor mange kilobyte er dette? Hvor mange kilobyte minne gjenstår å bruke til å lagre programmet vårt? Med andre ord, hvor stort kan programmet vårt være? Til slutt, hvor mange linjer med kode kan vi ha?

Ok, nå som vi vet alt om organisering av flash -programminnet, la oss fortsette med diskusjonen om.org -utsagnene. Vi ser at det første minnestedet 0x0000 inneholder instruksjonene våre om å hoppe til delen vi merket Reset. Nå ser vi hva uttalelsen ".org 0x0020" gjør. Det står at vi vil at instruksjonen på neste linje skal plasseres på minnested 0x0020. Instruksjonen vi har plassert der er et hopp til en seksjon i koden vår som vi har merket "overflow_handler" … nå hvorfor skulle vi kreve at dette hoppet ble plassert på minnested 0x0020? For å finne ut av det, går vi til side 65 i databladet og ser på tabell 12-6.

Tabell 12-6 er en tabell med "Reset and Interrupt Vectors", og den viser nøyaktig hvor PC-en vil gå når den mottar et "interrupt". For eksempel, hvis du ser på vektornummer 1. "Kilden" til avbruddet er "RESET", som er definert som "ekstern pin, oppstart for tilbakestilling, tilbakestilling av brun og nullstilling av vaktbikkje", hvis noen av disse tingene skjer med mikrokontrolleren vår, vil PC -en begynne å utføre programmet vårt på programminnet 0x0000. Hva med.org -direktivet vårt da? Vel, vi plasserte en kommando på minneplassering 0x0020, og hvis du ser ned i tabellen, vil du se at hvis det skjer et Timer/Counter0 -overløp (kommer fra TIMER0 OVF), vil det utføre det som er på stedet 0x0020. Så når det skjer, vil PC -en hoppe til stedet vi merket "overflow_handler". Kult, ikke sant? Du vil om et øyeblikk se hvorfor vi gjorde dette, men la oss først fullføre dette trinnet i opplæringen med en side.

Hvis vi ønsker å gjøre koden mer ryddig og ryddig, bør vi virkelig erstatte de 4 linjene vi diskuterer med følgende (se side 66):

.org 0x0000

rjmp Tilbakestill; 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å at hvis et gitt avbrudd oppstår, vil det bare "reti" som betyr "retur fra avbrudd" og ingenting annet skjer. Men hvis vi aldri "Aktiver" disse forskjellige avbruddene, vil de ikke bli brukt, og vi kan sette programkode på disse stedene. I vårt nåværende "blink.asm" -program skal vi bare aktivere avbrudd av timer0 -overløp (og selvfølgelig tilbakestillingsavbruddet som alltid er aktivert), og vi skal ikke bry oss med de andre.

Hvordan "aktiverer" vi timer0 -overløpsavbrudd da? … det er temaet for vårt neste trinn i denne opplæringen.

Trinn 5: Timer/teller 0

Timer/teller 0
Timer/teller 0

Ta en titt på bildet ovenfor. Dette er beslutningsprosessen for "PC" når noen påvirkning utenfra "avbryter" strømmen av programmet vårt. Det første den gjør når den får et signal fra utsiden om at det har oppstått et avbrudd, er at den sjekker om vi har angitt "avbryt aktiverings" -biten for den typen avbrudd. Hvis vi ikke har gjort det, fortsetter det bare å utføre vår neste kodelinje. Hvis vi har angitt den aktuelle avbruddsaktiveringsbiten (slik at det er en 1 på den bitplasseringen i stedet for en 0), vil den deretter kontrollere om vi har aktivert "globale avbrudd" eller ikke, hvis ikke går den igjen til neste linje av koden og fortsett. Hvis vi også har aktivert globale avbrudd, vil det gå til programminnelokasjonen for den typen avbrudd (som vist i tabell 12-6) og utføre hvilken kommando vi har plassert der. Så la oss se hvordan vi har implementert alt dette i koden vår.

Tilbakestill merket delen av koden vår begynner med følgende to linjer:

Nullstille:

ldi temp, 0b00000101 out TCCR0B, temp

Som vi allerede vet, lastes dette inn i temp (dvs. R16) tallet umiddelbart etter, som er 0b00000101. Deretter skriver det ut dette nummeret til registeret TCCR0B ved å bruke kommandoen "out". Hva er dette registeret? La oss gå til side 614 i databladet. Dette er i midten av en tabell som oppsummerer alle registerene. På adressen 0x25 finner du TCCR0B. (Nå vet du hvor linjen "out 0x25, r16" kom fra i min ikke-kommenterte versjon av koden). Vi ser ved kodesegmentet ovenfor at vi har satt den 0. og den andre bit og fjernet resten. Ved å se på tabellen kan du se at dette betyr at vi har satt CS00 og CS02. La oss nå gå over til kapitlet i databladet som heter "8-bit Timer/Counter0 with PWM". Gå spesielt til side 107 i det kapitlet. Du vil se den samme beskrivelsen av "Timer/Counter Control Register B" (TCCR0B) -registeret som vi nettopp så i registeroppsummeringstabellen (så vi kunne ha kommet rett hit, men jeg ville at du skulle se hvordan du bruker sammendragstabellene for framtidig referanse). Databladet fortsetter å gi en beskrivelse av hver av bitene i det registret og hva de gjør. Vi hopper over alt det nå og snu siden til tabell 15-9. Denne tabellen viser "Klokkevalg bitbeskrivelse". Se nå nedover tabellen til du finner linjen som tilsvarer bitene vi nettopp satte i det registeret. Linjen sier "clk/1024 (fra prescaler)". Hva dette betyr er at vi vil at Timer/Counter0 (TCNT0) skal krysse av med en hastighet som er CPU -frekvensen delt på 1024. Siden vi har mikrokontrolleren vår matet av en 16MHz krystalloscillator betyr det at frekvensen som CPUen vår utfører instruksjoner er 16 millioner instruksjoner per sekund. Så hastigheten som vår TCNT0 -teller vil krysse av er da 16 millioner/1024 = 15625 ganger i sekundet (prøv det med forskjellige klokkevalgbiter og se hva som skjer - husker du filosofien vår?). La oss beholde tallet 15625 i bakhodet for senere og gå videre til de neste to kodelinjene:

ldi temp, 0b00000001

m TIMSK0, temp

Dette setter den 0. biten av et register kalt TIMSK0 og sletter resten. Hvis du tar en titt på side 109 i databladet, vil du se at TIMSK0 står for "Timer/Counter Interrupt Mask Register 0" og koden vår har satt den 0. biten som heter TOIE0 som står for "Timer/Counter0 Overflow Interrupt Enable" … Der! Nå ser du hva dette handler om. Vi har nå "interrupt enable bit set" slik vi ønsket fra den første avgjørelsen i bildet vårt øverst. Så nå er alt vi trenger å gjøre å aktivere "globale avbrudd", og programmet vårt vil kunne svare på denne typen avbrudd. Vi vil aktivere globale avbrudd innen kort tid, men før vi gjør det kan det hende du har blitt forvirret av noe.. hvorfor i all verden brukte jeg kommandoen "sts" for å kopiere til TIMSK0 -registeret i stedet for det vanlige "ut"?

Når du ser meg bruke en instruksjon som du ikke har sett før det første du bør gjøre er å gå til side 616 i databladet. Dette er "Sammendrag av instruksjonssett". Finn nå instruksjonen "STS" som er den jeg brukte. Det står at det tar et tall fra et R -register (vi brukte R16) og "Lagre direkte til SRAM" -sted k (i vårt tilfelle gitt av TIMSK0). Så hvorfor måtte vi bruke "m" som tar 2 klokkesykluser (se siste kolonne i tabellen) for å lagre i TIMSK0, og vi trengte bare "ut", som bare tar en klokkesyklus, for å lagre i TCCR0B før? For å svare på dette spørsmålet må vi gå tilbake til registeroppsummeringstabellen på side 614. Du ser at TCCR0B -registeret er på adressen 0x25, men også på (0x45)? Dette betyr at det er et register i SRAM, men det er også en viss type register som kalles en "port" (eller i/o -register). Hvis du ser på instruksjonssammendragstabellen ved siden av "ut" -kommandoen, vil du se at den tar verdier fra "arbeidsregistrene" som R16 og sender dem til en PORT. Så vi kan bruke "out" når vi skriver til TCCR0B og spare oss en klokkesyklus. Men nå, slå opp TIMSK0 i registertabellen. Du ser at den har adressen 0x6e. Dette er utenfor portområdet (som bare er de første 0x3F -stedene til SRAM), og du må derfor gå tilbake til å bruke sts -kommandoen og ta to CPU -klokkesykluser for å gjøre det. Vennligst les note 4 på slutten av instruksjonsoversiktstabellen på side 615 akkurat nå. Legg også merke til at alle våre inngangs- og utgangsporter, som PORTD, er plassert nederst i tabellen. For eksempel er PD4 bit 4 på adresse 0x0b (nå ser du hvor alle 0x0b-tingene kom fra i min ukommenterte kode!).. ok, raskt spørsmål: endret du "m" til "ut" og så hva skjer? Husk filosofien vår! ødelegg det! ikke bare ta mitt ord for ting.

Ok, før vi går videre, gå til side 19 i databladet i et minutt. Du ser et bilde av dataminnet (SRAM). De første 32 registrene i SRAM (fra 0x0000 til 0x001F) er de "generelle arbeidsregistrene" R0 til R31 som vi hele tiden bruker som variabler i koden vår. De neste 64 registerene er I/O-portene opp til 0x005f (dvs. de vi snakket om som har de ikke-parenteserte adressene ved siden av dem i registertabellen som vi kan bruke kommandoen "ut" i stedet for "m") Til slutt den neste delen av SRAM inneholder alle de andre registerene i sammendragstabellen opp til adresse 0x00FF, og til slutt er resten intern SRAM. La oss raskt gå til side 12 for et sekund. Der ser du en tabell med "arbeidsregistre for generelle formål" som vi alltid bruker som våre variabler. Ser du den tykke linjen mellom tallene R0 til R15 og deretter R16 til R31? Den linjen er derfor vi alltid bruker R16 som den minste, og jeg vil komme inn på det litt mer i den neste opplæringen, hvor vi også trenger de tre 16-biters indirekte adresseregistrene, X, Y og Z. Jeg vil ikke gå inn på det ennå, men siden vi ikke trenger det nå, og vi blir fast nok her.

Vend en side tilbake til side 11 i databladet. Du vil se et diagram over SREG -registeret øverst til høyre? Du ser at bit 7 i registret kalles "I". Gå nå ned på siden og les beskrivelsen av Bit 7 …. Jippi! Det er Global Interrupt Enable -biten. Det er det vi må stille inn for å passere den andre avgjørelsen i diagrammet ovenfor og tillate avbrudd i timer/telleroverløp i programmet vårt. Så neste linje i programmet vårt skal lyde:

sbi SREG, jeg

som angir biten kalt "I" i SREG -registeret. Imidlertid har vi i stedet for dette brukt instruksjonene

sei

i stedet. Denne biten er satt så ofte i programmer at de bare har gjort en enklere måte å gjøre det på.

Greit! Nå har vi gjort overløpsavbruddene klare til å gå slik at vår "jmp overflow_handler" vil bli utført når en oppstår.

Før vi går videre, ta en rask titt på SREG -registeret (statusregister) fordi det er veldig viktig. Les hva hvert av flaggene representerer. Spesielt vil mange av instruksjonene vi bruker sette og sjekke disse flaggene hele tiden. For eksempel vil vi senere bruke kommandoen "CPI" som betyr "sammenligne umiddelbart". Ta en titt på instruksjonssammendragstabellen for denne instruksjonen, og legg merke til hvor mange flagg den angir i "flagg" -kolonnen. Dette er alle flagg i SREG, og koden vår vil sette dem og sjekke dem hele tiden. Du vil se eksempler snart. Til slutt er den siste biten av denne delen av koden:

clr temp

ut TCNT0, temp sbi DDRD, 4

Den siste linjen her er ganske åpenbar. Den angir bare den fjerde biten i Data Direction Register for PortD, noe som gjør at PD4 blir OUTPUT.

Den første setter variabelen temp til null og kopierer den deretter til TCNT0 -registeret. TCNT0 er vår timer/teller 0. Dette setter det til null. Så snart PC -en kjører denne linjen, starter timeren 0 på null og teller med en hastighet på 15625 ganger hvert sekund. Problemet er dette: TCNT0 er et "8-biters" register ikke sant? Så hva er det største antallet et 8-biters register kan inneholde? Vel, 0b11111111 er det. Dette er tallet 0xFF. Som er 255. Så du ser hva som skjer? Timeren glir sammen med å øke 15625 ganger i sekundet, og hver gang den når 255 "overløper" den og går tilbake til 0 igjen. På samme tid som det går tilbake til null sender det ut et Timer Overflow Interrupt -signal. PC -en får dette, og du vet hva den gjør nå? Jepp. Den går til Programminne -plassering 0x0020 og utfører instruksjonene den finner der.

Flott! Hvis du fortsatt er med meg, er du en utrettelig superhelt! La oss fortsette …

Trinn 6: Overløpshåndterer

Så la oss anta at timer/counter0 -registeret nettopp har flommet over. Vi vet nå at programmet mottar et avbruddssignal og kjører 0x0020 som forteller Programtelleren, PCen til å hoppe til etiketten "overflow_handler" følgende er koden vi skrev etter den etiketten:

overflow_handler:

inc overflows cpi overflows, 61 brne PC+2 clr overflows reti

Det første den gjør er å øke variabelen "overløp" (som er vårt navn for arbeidsregister for generelle formål R17), deretter "sammenligne" innholdet i overløp med tallet 61. Måten instruksjonen cpi fungerer på er at den ganske enkelt trekker fra de to tallene, og hvis resultatet er null, setter det Z -flagget i SREG -registret (jeg fortalte deg at vi ville se dette registret hele tiden). Hvis de to tallene er like, vil Z -flagget være et 1, hvis de to tallene ikke er like, vil det være et 0.

Den neste linjen sier "brne PC+2" som betyr "gren hvis ikke lik". I hovedsak sjekker det Z -flagget i SREG, og hvis det IKKE er ett (dvs. at de to tallene ikke er like, hvis de var like, ville nullflagget settes) grener PC -en til PC+2, noe som betyr at den hopper over den neste linje og går rett til "reti" som går tilbake fra avbruddet til hvilket sted det var i koden da avbruddet kom. Hvis brne -instruksjonen fant en 1 i nullflaggbiten, ville den ikke forgrene seg, og i stedet ville den bare fortsette til neste linje som ville overflytte clr og tilbakestille den til 0.

Hva er nettoresultatet av alt dette?

Vel, vi ser at hver gang det er et timeroverløp, øker denne behandleren verdien av "overløp" med en. Så variabelen "overløp" teller antall overløp når de oppstår. Når tallet når 61 nullstiller vi det til null.

Hvorfor i all verden skulle vi gjøre det?

La oss se. Husker du at klokkehastigheten for CPU -en vår er 16 MHz og at vi "forhåndskalkulerte" den med TCCR0B, slik at timeren bare teller med en hastighet på 15625 tellinger per sekund? Og hver gang timeren når 255 teller den over. Så det betyr at det flyter over 15625/256 = 61,04 ganger i sekundet. Vi holder orden på antall overløp med variabelen "overløp", og vi sammenligner tallet med 61. Så vi ser at "overløp" vil være 61 en gang hvert sekund! Så vår handler vil tilbakestille "overløp" til null en gang hvert sekund. Så hvis vi bare skulle overvåke variabelen "overløp" og notere oss hver gang den nullstilles til null, ville vi telle sekund for sekund i sanntid (Merk at i den neste opplæringen vil vi vise hvordan vi får en mer nøyaktig forsinkelse i millisekunder på samme måte som Arduino "forsinkelse" -rutinen fungerer).

Nå har vi "håndtert" tidsavbruddene for timeroverløp. Sørg for at du forstår hvordan dette fungerer, og fortsett deretter til neste trinn der vi bruker dette faktum.

Trinn 7: Forsinkelse

Nå som vi har sett at ruteren for overløpshåndtering av timer for overløp "overflow_handler" vil sette variabelen "overløp" til null en gang hvert sekund, kan vi bruke dette faktum til å designe en "forsinkelse" underprogram.

Ta en titt på følgende kode under forsinkelsen vår: etikett

forsinkelse:

clr overløper sec_count: cpi overløper, 30 brne sec_count ret

Vi kommer til å kalle denne subrutinen hver gang vi trenger en forsinkelse i programmet vårt. Måten den fungerer på, setter den først variabelen "overløp" til null. Deretter går det inn i et område merket "sec_count" og sammenligner overløp med 30, hvis de ikke er like, grener det seg tilbake til etiketten sec_count og sammenligner igjen, og igjen, etc. til de til slutt er like (husk at hele tiden dette går på vår timer -interrupt -behandler fortsetter å øke variabelen overløp og det endrer seg hver gang vi går rundt her. Når overløp til slutt er lik 30 kommer den ut av sløyfen og går tilbake til hvor vi kalte forsinkelse: fra. Nettoresultatet er et forsinkelse på 1/2 sekund

Oppgave 2: Endre overflow_handler -rutinen til følgende:

overflow_handler:

inc overfyller reti

og kjør programmet. Er noe annerledes? Hvorfor eller hvorfor ikke?

Trinn 8: Blink

Til slutt skal vi se på blinkrutinen:

blinke:

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

Først slår vi på PD4, deretter ringer vi forsinkelsessubrutinen. Vi bruker rcall slik at når PC -en kommer til en "ret" -erklæring, kommer den tilbake til linjen etter rcall. Deretter forsinker forsinkelsesrutinen for 30 tellinger i overløpsvariabelen som vi har sett, og dette er nesten nøyaktig 1/2 sekund, så slår vi av PD4, forsinker ytterligere 1/2 sekund, og går deretter tilbake til begynnelsen igjen.

Nettoresultatet er en blinkende LED!

Jeg tror du nå vil være enig i at "blink" sannsynligvis ikke er det beste "hei verden" -programmet i samlingsspråk.

Øvelse 3: Endre de forskjellige parameterne i programmet slik at LED -en blinker med forskjellige hastigheter som et sekund eller 4 ganger i sekundet, etc. Øvelse 4: Endre den slik at LED -en er på og av i forskjellige tidsrom. For eksempel på i 1/4 sekund og deretter av i 2 sekunder eller noe sånt. Øvelse 5: Endre TCCR0B -klokken, velg biter til 100 og fortsett deretter oppover tabellen. På hvilket tidspunkt kan det ikke skilles fra vårt "hello.asm" -program fra opplæring 1? Øvelse 6 (valgfritt): Hvis du har en annen krystalloscillator, for eksempel en 4 MHz eller en 13,5 MHz eller hva som helst, bytt 16 MHz -oscillator på brettbrettet for det nye og se hvordan det påvirker LED -blinkens blinkhastighet. Du bør nå kunne gå gjennom den nøyaktige beregningen og forutsi nøyaktig hvordan det vil påvirke hastigheten.

Trinn 9: Konklusjon

Til dere som har kommet så langt, gratulerer!

Jeg skjønner at det er ganske tøft når du leser og ser mer enn du kabler og eksperimenterer, men jeg håper du har lært følgende viktige ting:

  1. Hvordan programminne fungerer
  2. Hvordan SRAM fungerer
  3. Hvordan slå opp registre
  4. Hvordan slå opp instruksjoner og vite hva de gjør
  5. Hvordan implementere avbrudd
  6. Hvordan CP utfører koden, hvordan SREG fungerer, og hva som skjer under avbrudd
  7. Hvordan gjøre sløyfer og hopp og sprette rundt i koden
  8. Hvor viktig det er å lese databladet!
  9. Hvordan når du vet hvordan du gjør alt dette for Atmega328p mikrokontroller, vil det være en relativ kakevandring å lære om nye kontrollere som du er interessert i.
  10. Hvordan endre CPU -tid til sanntid og bruke den i forsinkelsesrutiner.

Nå som vi har mye teori ute av måten vi er i stand til å skrive bedre kode og kontrollere mer kompliserte ting. Så den neste opplæringen skal vi gjøre nettopp det. Vi skal bygge en mer komplisert, mer interessant, krets og kontrollere den på morsomme måter.

Oppgave 7: "Bryt" koden på forskjellige måter og se hva som skjer! Vitenskapelig nysgjerrighet baby! Noen andre kan vaske oppvasken riktig? Øvelse 8: Sett sammen koden ved hjelp av alternativet "-l" for å generere en listefil. Dvs. "avra -l blink.lst blink.asm" og ta en titt på listefilen. Ekstra kreditt: Den ukommenterte koden som jeg ga i begynnelsen, og den kommenterte koden som vi diskuterer senere, er forskjellige! Det er en linje med kode som er annerledes. Kan du finne den? Hvorfor spiller ikke den forskjellen noen rolle?

Håper du hadde det gøy! Vi sees neste gang …

Anbefalt: