Indholdsfortegnelse:

AVR Assembler Tutorial 3: 9 trin
AVR Assembler Tutorial 3: 9 trin

Video: AVR Assembler Tutorial 3: 9 trin

Video: AVR Assembler Tutorial 3: 9 trin
Video: AVR Assembly Tutorial: Part 1 (Basic Commands) 2024, November
Anonim
Vejledning til AVR -samler 3
Vejledning til AVR -samler 3

Velkommen til tutorial nummer 3!

Inden vi går i gang, vil jeg gøre en filosofisk pointe. Vær ikke bange for at eksperimentere med kredsløbene og koden, som vi konstruerer i disse selvstudier. Skift ledninger rundt, tilføj nye komponenter, tag komponenter ud, skift kodelinjer, tilføj nye linjer, slet linjer, og se hvad der sker! Det er meget svært at bryde noget, og hvis du gør, hvem bekymrer sig? Intet vi bruger, inklusive mikrokontrolleren, er meget dyrt, og det er altid lærerigt at se, hvordan tingene kan mislykkes. Ikke alene finder du ud af, hvad du ikke skal gøre næste gang, men endnu vigtigere, du ved, hvorfor du ikke skal gøre det. Hvis du ligner mig, var det ikke meget længe, da du var barn, og du fik et nyt legetøj, før du havde det i stykker for at se, hvad der fik det til at krydse rigtigt? Nogle gange endte legetøjet uopretteligt beskadiget, men ikke så farligt. At tillade et barn at udforske sin nysgerrighed, selv om det er i stykker legetøj, er det, der gør ham til en videnskabsmand eller ingeniør i stedet for en opvaskemaskine.

I dag skal vi tilslutte et meget enkelt kredsløb og derefter komme lidt tungt ind i teorien. Beklager dette, men vi har brug for værktøjerne! Jeg lover, at vi vil gøre op med dette i tutorial 4, hvor vi skal lave en mere seriøs kredsløbsopbygning, og resultatet bliver ret sejt. Den måde, du skal udføre alle disse selvstudier på, er imidlertid en meget langsom, kontemplativ måde. Hvis du bare pløjer igennem, bygger du kredsløbet, kopierer og indsætter koden og kører det derefter, det fungerer sikkert, men du lærer ikke noget. Du skal tænke over hver linje. Pause. Eksperiment. Opfind. Hvis du gør det på den måde, vil du ved slutningen af den 5. vejledning være i gang med at bygge fede ting og ikke behøver mere vejledning. Ellers ser du simpelthen frem for at lære og skabe.

Under alle omstændigheder nok filosofi, lad os komme i gang!

I denne tutorial skal du bruge:

  1. dit prototypebord
  2. en LED
  3. tilslutning af ledninger
  4. en modstand omkring 220 til 330 ohm
  5. Instruktionssætmanualen: www.atmel.com/images/atmel-0856-avr-instruction-se…
  6. Databladet: www.atmel.com/images/Atmel-8271-8-bit-AVR-Microco…
  7. en anden krystaloscillator (valgfri)

Her er et link til den komplette samling af selvstudier:

Trin 1: Konstruktion af kredsløbet

Konstruktion af kredsløbet
Konstruktion af kredsløbet

Kredsløbet i denne vejledning er ekstremt enkelt. Vi vil hovedsageligt skrive "blink" -programmet, så alt hvad vi har brug for er følgende.

Tilslut en LED til PD4, derefter til en 330 ohm modstand, derefter til jorden. dvs.

PD4 - LED - R (330) - GND

og det er det!

Teorien bliver dog en hård slogging …

Trin 2: Hvorfor har vi brug for kommentarerne og filen M328Pdef.inc?

Jeg synes, vi skal starte med at vise, hvorfor inkluderingsfilen og kommentarerne er nyttige. Ingen af dem er faktisk nødvendige, og du kan skrive, samle og uploade kode på samme måde uden dem, og det vil køre perfekt godt (selvom du kan få nogle klager fra samleren uden inkluderingsfilen - men ingen fejl)

Her er den kode, vi skal skrive i dag, bortset fra at jeg har fjernet kommentarerne og inkluder filen:

.enhed 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, 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

ret enkelt ikke? Haha. Hvis du har samlet og uploadet denne fil, får du LED'en til at blinke med en hastighed på 1 blink pr. Sekund, med blinket i 1/2 sekund og pausen mellem blink i 1/2 sekund.

Men at se på denne kode er næppe oplysende. Hvis du skulle skrive kode som denne, og du ville ændre den eller genbruge den i fremtiden, ville du have det svært.

Så lad os lægge kommentarerne og inkludere filen tilbage, så vi kan få en fornemmelse af det.

Trin 3: Blink.asm

Her er koden, vi vil diskutere i dag:

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

; skrevet af: 1o_o7; dato:; version: 1.0; fil gemt som: blink.asm; til AVR: atmega328p; urfrekvens: 16MHz (valgfri); *************************************; Programfunktion: ---------------------; tæller sekunder af ved at blinke en LED;; PD4 - LED - R (330 ohm) - GND;; --------------------------------------.nolist. inkludere "./m328Pdef.inc".list; ===============; Deklarationer:.def temp = r16.def overflows = r17.org 0x0000; hukommelse (pc) placering af reset handler rjmp Nulstil; jmp koster 2 cpu cyklusser og rjmp koster kun 1; så medmindre du skal springe mere end 8k bytes; du behøver kun rjmp. Nogle mikrokontrollere derfor kun; har rjmp og ikke jmp.org 0x0020; hukommelsesplacering af Timer0 overflow handler rjmp overflow_handler; gå her, hvis der forekommer et afbrydelse af timer0 -overløb; ============ Nulstil: ldi temp, 0b00000101 out TCCR0B, temp; indstil urvælgerbitene CS00, CS01, CS02 til 101; dette sætter Timer Counter0, TCNT0 i FCPU/1024 mode; så det tikker ved CPU -frekvensen/1024 ldi temp, 0b00000001 st TIMSK0, temp; indstil Timer Overflow Interrupt Enable (TOIE0) bit; af Timer Interrupt Mask Register (TIMSK0) sei; muliggøre globale afbrydelser - svarende til "sbi SREG, I" clr temp out TCNT0, temp; initialiser timeren/tælleren til 0 sbi DDRD, 4; indstil PD4 til output; ========================= Programmets hoveddel: blink: sbi PORTD, 4; tænde LED på PD4 rcall forsinkelse; forsinkelse vil være 1/2 sekund cbi PORTD, 4; sluk LED for PD4 rcall forsinkelse; forsinkelse vil være 1/2 sekund rjmp blink; loop tilbage til startforsinkelsen: clr flyder over; sæt overløb til 0 sec_count: cpi overflows, 30; sammenligne antal overløb og 30 brne sec_count; gren til tilbage til sec_count hvis ikke lig ret; hvis der er opstået 30 overløb, vender tilbage til blink overflow_handler: inc overflows; tilføj 1 til overløbsvariablen cpi overflows, 61; sammenligne med 61 brne PC+2; Programtæller + 2 (spring næste linje over) hvis ikke lige clr -overløb; nulstil tælleren til nul reti, hvis der opstod 61 overløb; vende tilbage fra afbrydelse

Som du kan se, er mine kommentarer lidt mere korte nu. Når vi ved, hvad kommandoerne i instruktionssættet behøver, behøver vi ikke at forklare det i kommentarer. Vi behøver kun at forklare, hvad der foregår ud fra programmets synspunkt.

Vi vil diskutere, hvad alt dette gør stykke for stykke, men lad os først prøve at få et globalt perspektiv. Programmets hoveddel fungerer som følger.

Først satte vi bit 4 af PORTD med "sbi PORTD, 4", dette sender en 1 til PD4, som sætter spændingen til 5V på den pin. Dette vil tænde LED'en. Vi springer derefter til "forsinkelsen" -underrutinen, som tæller 1/2 sekund ud (vi forklarer, hvordan den gør dette senere). Vi vender derefter tilbage for at blinke og rydde bit 4 på PORTD, som indstiller PD4 til 0V og derfor slukker LED'en. Vi forsinker derefter yderligere 1/2 sekund, og hopper derefter tilbage til begyndelsen af blink igen med "rjmp blink".

Du skal køre denne kode og se, at den gør, hvad den skal.

Og der har du det! Det er alt, hvad denne kode gør fysisk. Den interne mekanik i, hvad mikrokontrolleren laver, er lidt mere involveret, og derfor laver vi denne vejledning. Så lad os diskutere hvert afsnit efter tur.

Trin 4:.org Assembler -direktiver

Vi ved allerede, hvad.nolist,.list,.include og.def assembler -direktiverne gør fra vores tidligere selvstudier, så lad os først se på de 4 kodelinjer, der kommer efter det:

.org 0x0000

jmp Nulstil.org 0x0020 jmp overflow_handler

. Org -sætningen fortæller samleren, hvor i "Programhukommelse" den næste sætning skal placeres. Når dit program udføres, indeholder "Programtælleren" (forkortet som PC) adressen på den aktuelle linje, der udføres. Så i dette tilfælde, når pc'en er på 0x0000, vil den se kommandoen "jmp Reset", der ligger i den pågældende hukommelsesplacering. Grunden til, at vi vil sætte jmp Reset på det sted, er, at når programmet starter, eller chippen nulstilles, begynder pc'en at eksekvere kode på dette sted. Så som vi kan se, har vi lige fortalt det at straks "hoppe" til afsnittet mærket "Nulstil". Hvorfor gjorde vi det? Det betyder, at de to sidste linjer ovenfor bare bliver sprunget over! Hvorfor?

Jamen det er her tingene bliver interessante. Du bliver nu nødt til at åbne en pdf -fremviser med det fulde ATmega328p -datablad, som jeg pegede på på den første side i denne vejledning (derfor er det punkt 4 i afsnittet "du skal bruge"). Hvis din skærm er for lille, eller du allerede har alt for mange vinduer åbne (som det er tilfældet med mig), kan du gøre, hvad jeg gør, og sætte den på en Ereader eller din Android -telefon. Du vil bruge det hele tiden, hvis du planlægger at skrive samlingskode. Det fede er, at alle mikrokontrollere er organiseret på meget lignende måder, og så når du først er vant til at læse datablade og koder fra dem, vil du finde det næsten trivielt at gøre det samme for en anden mikrokontroller. Så vi er faktisk ved at lære at bruge alle mikrokontrollere på en måde og ikke kun atmega328p.

Okay, gå til side 18 i databladet og se på Figur 8-2.

Sådan er programhukommelsen i mikrokontrolleren konfigureret. Du kan se, at det starter med adressen 0x0000 og er opdelt i to sektioner; en applikationsflits sektion og en boot flash sektion. Hvis du kort refererer til side 277 tabel 27-14, vil du se, at applikationsflitsen optager lokaliteterne fra 0x0000 til 0x37FF, og boot-flash-sektionen optager de resterende placeringer fra 0x3800 til 0x3FFF.

Øvelse 1: Hvor mange steder er der i programhukommelsen? Dvs. konverter 3FFF til decimal og tilføj 1, da vi begynder at tælle til 0. Da er hvert hukommelsessted 16 bit (eller 2 bytes) bredt, hvad er det samlede antal bytes hukommelse? Konverter nu dette til kilobyte, husk at der er 2^10 = 1024 bytes i en kilobyte. Boot flash -sektionen går fra 0x3800 til 0x37FF, hvor mange kilobytes er dette? Hvor mange kilobyte hukommelse er tilbage, som vi kan bruge til at gemme vores program? Med andre ord, hvor stort kan vores program være? Endelig, hvor mange linjer kode kan vi have?

Okay, nu hvor vi ved alt om organiseringen af flash -programhukommelsen, lad os fortsætte med vores diskussion af.org -udsagnene. Vi ser, at den første hukommelsesplacering 0x0000 indeholder vores instruktion om at springe til vores sektion, vi markerede Nulstil. Nu ser vi, hvad ".org 0x0020" -udsagnet gør. Det siger, at vi ønsker, at instruktionen på den næste linje skal placeres på hukommelsesstedet 0x0020. Den instruktion, vi har placeret der, er et spring til et afsnit i vores kode, som vi har mærket "overflow_handler" … nu hvorfor skulle vi kræve, at dette spring blev placeret på hukommelsessted 0x0020? For at finde ud af det går vi til side 65 i databladet og kigger på tabel 12-6.

Tabel 12-6 er en tabel med "Nulstil og afbryd vektorer", og den viser præcis, hvor pc'en vil gå hen, når den modtager en "afbrydelse". For eksempel, hvis du kigger på vektornummer 1. "Kilden" til afbrydelsen er "RESET", der er defineret som "Ekstern pin, tænding nulstilling, nulstilling af brown-out og nulstilling af Watchdog-system", hvis nogen af disse ting sker med vores mikrokontroller, vil pc'en begynde at udføre vores program på programmets hukommelsessted 0x0000. Hvad med vores.org -direktiv så? Godt, vi placerede en kommando på hukommelsesplacering 0x0020, og hvis du kigger ned i tabellen, vil du se, at hvis der sker et Timer/Counter0 -overløb (kommer fra TIMER0 OVF), vil det udføre det, der er på stedet 0x0020. Så når det sker, springer pc'en til det sted, vi betegnede "overflow_handler". Fedt ikke? Du vil om et øjeblik se, hvorfor vi gjorde dette, men lad os først afslutte dette trin i selvstudiet med en side.

Hvis vi vil gøre vores kode mere pæn og ryddig, bør vi virkelig erstatte de 4 linjer, vi diskuterer i øjeblikket, med følgende (se side 66):

.org 0x0000

rjmp Nulstil; 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å hvis en given afbrydelse sker, vil den bare "reti", hvilket betyder "retur fra afbrydelse", og intet andet sker. Men hvis vi aldrig "Aktiverer" disse forskellige afbrydelser, så vil de ikke blive brugt, og vi kan sætte programkode på disse steder. I vores nuværende "blink.asm" program vil vi kun aktivere timer0 overløbsafbrydelsen (og selvfølgelig nulstillingsafbrydelsen, som altid er aktiveret), og så vil vi ikke genere med de andre.

Hvordan "aktiverer" vi timer0 -overløbsafbrydelsen så? … det er emnet for vores næste trin i denne vejledning.

Trin 5: Timer/tæller 0

Timer/tæller 0
Timer/tæller 0

Tag et kig på ovenstående billede. Dette er beslutningsprocessen for "PC", når en vis påvirkning udefra "afbryder" strømmen af vores program. Den første ting, den gør, når den får et signal udefra, at der er opstået et afbrydelse, er, at den kontrollerer, om vi har indstillet "afbryd aktiverings" -biten for den type afbrydelse. Hvis vi ikke har gjort det, fortsætter det bare med at udføre vores næste kodelinje. Hvis vi har indstillet den pågældende afbrydelsesaktiveringsbit (så der er en 1 i den bitplacering i stedet for en 0), vil den derefter kontrollere, om vi har aktiveret "globale afbrydelser", hvis ikke vil den igen gå til den næste linje kode og fortsæt. Hvis vi også har aktiveret globale afbrydelser, vil det gå til programhukommelsesplaceringen for den type afbrydelse (som vist i tabel 12-6) og udføre den kommando, vi har placeret der. Så lad os se, hvordan vi har implementeret alt dette i vores kode.

Afsnittet Nulstil mærket i vores kode begynder med følgende to linjer:

Nulstil:

ldi temp, 0b00000101 out TCCR0B, temp

Som vi allerede ved, indlæses dette i temp (dvs. R16) tallet umiddelbart efter, hvilket er 0b00000101. Derefter skriver det dette nummer ud til registret kaldet TCCR0B ved hjælp af kommandoen "out". Hvad er dette register? Lad os gå videre til side 614 i databladet. Dette er i midten af en tabel, der opsummerer alle registre. På adressen 0x25 finder du TCCR0B. (Nu ved du, hvor linjen "out 0x25, r16" kom fra i min ikke-kommenterede version af koden). Vi ser ved kodesegmentet ovenfor, at vi har indstillet den 0. bit og den 2. bit og ryddet resten. Ved at se på tabellen kan du se, at dette betyder, at vi har indstillet CS00 og CS02. Lad os nu gå over til kapitlet i databladet kaldet "8-bit Timer/Counter0 with PWM". Gå især til side 107 i dette kapitel. Du vil se den samme beskrivelse af "Timer/Counter Control Register B" (TCCR0B) -registret, som vi lige så i registeroversigtstabellen (så vi kunne være kommet direkte hertil, men jeg ville have dig til at se, hvordan du bruger oversigtstabellerne til fremtidig reference). Databladet fortsætter med at give en beskrivelse af hver af bitene i det register og hvad de gør. Vi springer alt det over for nu og vender siden til tabel 15-9. Denne tabel viser "Clock Select Bit Description". Se nu ned i tabellen, indtil du finder den linje, der svarer til de bits, vi lige har sat i det register. Linjen siger "clk/1024 (fra prescaler)". Hvad dette betyder er, at vi vil have Timer/Counter0 (TCNT0) til at krydse af med en hastighed, som er CPU -frekvensen divideret med 1024. Da vi har vores mikrokontroller fodret med en 16MHz krystaloscillator, betyder det, at den hastighed, som vores CPU udfører instruktioner, er 16 millioner instruktioner i sekundet. Så den hastighed, som vores TCNT0 -tæller vil markere, er derefter 16 millioner/1024 = 15625 gange i sekundet (prøv det med forskellige urvælger og se, hvad der sker - husk vores filosofi?). Lad os beholde tallet 15625 i baghovedet til senere og gå videre til de næste to linjer med kode:

ldi temp, 0b00000001

m TIMSK0, temp

Dette indstiller den 0. bit af et register kaldet TIMSK0 og sletter resten. Hvis du kigger på side 109 i databladet, vil du se, at TIMSK0 står for "Timer/Counter Interrupt Mask Register 0", og vores kode har angivet den 0. bit, der hedder TOIE0, som står for "Timer/Counter0 Overflow Interrupt Enable" … Der! Nu kan du se, hvad det her handler om. Vi har nu "interrupt enable bit set" som vi ønskede fra den første beslutning i vores billede øverst. Så nu er alt, hvad vi skal gøre, at aktivere "globale afbrydelser", og vores program vil være i stand til at reagere på denne type afbrydelser. Vi vil aktivere globale afbrydelser snart, men før vi gør det, er du muligvis blevet forvirret af noget.. hvorfor i alverden brugte jeg kommandoen "sts" til at kopiere til TIMSK0 -registret i stedet for det sædvanlige "ud"?

Når du ser mig, skal du bruge en instruktion, som du ikke har set før det første, du skal gøre, er at gå til side 616 i databladet. Dette er "Oversigt over instruktionssæt". Find nu instruktionen "STS", som er den, jeg brugte. Det siger, at det tager et nummer fra et R -register (vi brugte R16) og "Gem direkte til SRAM" -placering k (i vores tilfælde givet af TIMSK0). Så hvorfor skulle vi bruge "m", som tager 2 urcyklusser (se sidste kolonne i tabellen) for at gemme i TIMSK0, og vi havde kun brug for "out", som kun tager en urcyklus, for at gemme i TCCR0B før? For at besvare dette spørgsmål skal vi gå tilbage til vores registeroversigtstabel på side 614. Du kan se, at TCCR0B -registret er på adressen 0x25, men også på (0x45)? Det betyder, at det er et register i SRAM, men det er også en bestemt type register kaldet en "port" (eller i/o -register). Hvis du ser på instruktionsoversigtstabellen ved siden af "out" -kommandoen, vil du se, at den tager værdier fra "arbejdsregistre" som R16 og sender dem til en PORT. Så vi kan bruge "out", når vi skriver til TCCR0B og spare os selv en urcyklus. Men slå nu op TIMSK0 i registertabellen. Du kan se, at den har adressen 0x6e. Dette er uden for rækkevidde af porte (som kun er de første 0x3F -placeringer af SRAM), og derfor skal du falde tilbage til at bruge sts -kommandoen og tage to CPU -urcyklusser for at gøre det. Læs note 4 i slutningen af instruktionsoversigtstabellen på side 615 lige nu. Bemærk også, at alle vores input- og outputporte, som PORTD, er placeret i bunden af tabellen. For eksempel er PD4 bit 4 på adressen 0x0b (nu kan du se, hvor alle 0x0b-tingene kom fra i min kode uden kommentarer!).. okay, hurtigt spørgsmål: ændrede du "sts" til "out" og så hvad sker? Husk vores filosofi! Ødelæg det! ikke bare tage mit ord for ting.

Okay, før vi går videre, skal du gå til side 19 i databladet i et minut. Du ser et billede af datahukommelsen (SRAM). De første 32 registre i SRAM (fra 0x0000 til 0x001F) er de "almindelige arbejdsregistre" R0 til R31, som vi hele tiden bruger som variabler i vores kode. De næste 64 registre er I/O-portene op til 0x005f (dvs. dem, vi talte om, som har de ikke-parenteserede adresser ved siden af dem i registertabellen, som vi kan bruge kommandoen "out" i stedet for "sts") Endelig den næste sektion af SRAM indeholder alle de andre registre i oversigtstabellen op til adressen 0x00FF, og til sidst er resten intern SRAM. Lad os nu hurtigt vende til side 12 et sekund. Der ser du en tabel over de "generelle arbejdsregistre", som vi altid bruger som vores variabler. Ser du den tykke linje mellem tallene R0 til R15 og derefter R16 til R31? Den linje er derfor, vi altid bruger R16 som den mindste, og jeg vil komme lidt mere ind på det i den næste vejledning, hvor vi også får brug for de tre 16-bit indirekte adresseregistre, X, Y og Z. Jeg vil ikke komme ind på det endnu endnu, da vi ikke har brug for det nu, og vi er ved at blive stødt nok ned her.

Vend en side tilbage til side 11 i databladet. Du vil se et diagram over SREG -registret øverst til højre? Du kan se, at bit 7 i det register kaldes "I". Gå nu ned på siden og læs beskrivelsen af Bit 7 …. yay! Det er Global Interrupt Enable -bit. Det er det, vi skal indstille for at passere den anden beslutning i vores diagram ovenfor og tillade afbrydelser af timer/tælleroverløb i vores program. Så den næste linje i vores program skulle lyde:

sbi SREG, jeg

som sætter den bit, der hedder "I" i SREG -registret. Men frem for dette har vi brugt instruktionen

sei

i stedet. Denne bit er sat så ofte i programmer, at de bare har gjort en enklere måde at gøre det på.

Okay! Nu har vi gjort overløbsafbrydelserne klar til at gå, så vores "jmp overflow_handler" vil blive udført, når der opstår en.

Inden vi går videre, skal du tage et hurtigt kig på SREG -registret (statusregister), fordi det er meget vigtigt. Læs hvad hvert af flagene repræsenterer. Især vil mange af de instruktioner, vi bruger, indstille og kontrollere disse flag hele tiden. For eksempel vil vi senere bruge kommandoen "CPI", hvilket betyder "sammenlign øjeblikkeligt". Tag et kig på instruktionsoversigtstabellen for denne instruktion, og læg mærke til, hvor mange flag den sætter i kolonnen "flag". Disse er alle flag i SREG, og vores kode vil sætte dem og kontrollere dem konstant. Du ser snart eksempler. Endelig er den sidste bit af denne sektion af kode:

clr temp

ud TCNT0, temp sbi DDRD, 4

Den sidste linje her er temmelig indlysende. Det indstiller bare den 4. bit i Data Direction Register for PortD, hvilket får PD4 til at være OUTPUT.

Den første sætter variabel temp til nul og kopierer derefter den ud til TCNT0 -registret. TCNT0 er vores timer/tæller0. Dette sætter det til nul. Så snart pc'en udfører denne linje, starter timeren 0 på nul og tæller med en hastighed på 15625 gange hvert sekund. Problemet er dette: TCNT0 er et "8-bit" register, ikke? Så hvad er det største antal, som et 8-bit register kan indeholde? Godt 0b11111111 er det. Dette er tallet 0xFF. Hvilket er 255. Så kan du se, hvad der sker? Timeren zipper langs med at stige 15625 gange i sekundet, og hver gang den når 255 "overløber" den og går tilbage til 0 igen. På samme tid som det går tilbage til nul, sender det et Timer Overflow Interrupt -signal ud. PC'en får dette, og du ved hvad det gør nu? Jep. Den går til Programhukommelseslokation 0x0020 og udfører den instruktion, den finder der.

Store! Hvis du stadig er hos mig, så er du en utrættelig superhelt! Lad os blive ved…

Trin 6: Overløbshåndterer

Så lad os antage, at timer/counter0 -registret lige er overfyldt. Vi ved nu, at programmet modtager et afbrydelsessignal og udfører 0x0020, der fortæller programtælleren, pc'en til at springe til etiketten "overflow_handler" følgende er koden, vi skrev efter denne etiket:

overflow_handler:

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

Den første ting, den gør, er at øge variablen "overløb" (som er vores navn for almindeligt arbejdsregister R17), så "sammenligner" den indholdet af overløb med tallet 61. Den måde, hvorpå instruktionen cpi fungerer, er, at den simpelthen trækker fra de to tal, og hvis resultatet er nul, sætter det Z -flag i SREG -registret (jeg fortalte dig, at vi ville se dette register hele tiden). Hvis de to tal er ens, vil Z -flag være et 1, hvis de to tal ikke er lige, vil det være et 0.

Den næste linje siger "brne PC+2", hvilket betyder "gren hvis ikke lige". I det væsentlige kontrollerer det Z -flag i SREG, og hvis det IKKE er et et (dvs. at de to tal ikke er ens, hvis de var lige, ville nulflaget være indstillet) grene pc'en til PC+2, hvilket betyder, at den springer over den næste linje og går direkte til "reti", som vender tilbage fra afbrydelsen til det sted, det var i koden, da afbrydelsen ankom. Hvis brne -instruktionen fandt en 1 i nul -flagbitten, ville den ikke forgrenes, og i stedet ville den bare fortsætte til den næste linje, som ville clr overløb nulstille den til 0.

Hvad er nettoresultatet af alt dette?

Vi ser godt, at hver gang der er et timeroverløb, øger denne handler værdien af "overløb" med en. Så variablen "overløb" tæller antallet af overløb, når de forekommer. Når tallet når 61, nulstiller vi det til nul.

Nu hvorfor i alverden ville vi gøre det?

Lad os se. Husk, at vores clockhastighed for vores CPU er 16MHz, og vi "forskalerede" den ved hjælp af TCCR0B, så timeren kun tæller med en hastighed på 15625 tællinger i sekundet, ikke? Og hver gang timeren når et tal på 255, flyder det over. Så det betyder, at det flyder over 15625/256 = 61,04 gange i sekundet. Vi holder styr på antallet af overløb med vores variabel "overløb", og vi sammenligner dette tal med 61. Så vi ser, at "overløb" vil være 61 en gang hvert sekund! Så vores handler vil nulstille "overløb" til nul en gang hvert sekund. Så hvis vi simpelthen skulle overvåge variablen "overløb" og notere hver gang den nulstilles til nul, ville vi tælle sekund for sekund i realtid (Bemærk, at i den næste vejledning viser vi, hvordan vi får en mere præcis forsinkelse i millisekunder på samme måde som Arduino "forsinkelse" -rutinen fungerer).

Nu har vi "håndteret" timeroverløbsafbrydelserne. Sørg for at forstå, hvordan dette fungerer, og gå derefter videre til det næste trin, hvor vi gør brug af denne kendsgerning.

Trin 7: Forsinkelse

Nu hvor vi har set, at vores timeroverløbsafbrydelseshandler "overflow_handler" -rutine sætter variablen "overløb" til nul en gang hvert sekund, kan vi bruge denne kendsgerning til at designe en "forsinkelse" underprogram.

Tag et kig på følgende kode under vores forsinkelse: etiket

forsinke:

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

Vi vil kalde dette underprogram hver gang vi har brug for en forsinkelse i vores program. Den måde, den fungerer på, er, at den først sætter variablen "overløb" til nul. Derefter går det ind i et område mærket "sec_count" og sammenligner overløb med 30, hvis de ikke er lige, forgrener det sig tilbage til etiketten sec_count og sammenligner igen og igen osv., Indtil de endelig er ens (husk at hele tiden dette går på vores timer afbryder handler fortsat med at øge variablen overløb, og det ændrer sig hver gang vi går rundt her. Når overløb endelig er lig med 30, kommer det ud af sløjfen og vender tilbage til det sted, hvor vi kaldte forsinkelse: fra. Nettoresultatet er et forsinkelse på 1/2 sekund

Øvelse 2: Skift rutinen overflow_handler til følgende:

overflow_handler:

inc overløber reti

og kør programmet. Er der noget anderledes? Hvorfor eller hvorfor ikke?

Trin 8: Blink

Lad os endelig se på blinkrutinen:

blinke:

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

Først tænder vi PD4, derefter kalder vi vores forsinkelsessubrutine. Vi bruger rcall, så når pc'en kommer til en "ret" -erklæring, kommer den tilbage til linjen efter rcall. Derefter forsinker forsinkelsesrutinen for 30 tællinger i overløbsvariablen, som vi har set, og dette er næsten præcis 1/2 sekund, derefter slukker vi PD4, forsinker yderligere 1/2 sekund og går derefter tilbage til begyndelsen igen.

Nettoresultatet er en blinkende LED!

Jeg tror, du nu vil være enig i, at "blink" nok ikke er det bedste "hej verden" -program i samlingssprog.

Øvelse 3: Skift de forskellige parametre i programmet, så LED'en blinker med forskellige hastigheder som et sekund eller 4 gange i sekundet osv. Øvelse 4: Skift det, så LED'en er tændt og slukket i forskellige tidsrum. For eksempel tændt i 1/4 sekund og derefter slukket i 2 sekunder eller sådan noget. Øvelse 5: Skift TCCR0B -uret vælg bits til 100, og fortsæt derefter med at gå op ad tabellen. På hvilket tidspunkt kan det ikke skelnes fra vores "hello.asm" -program fra øvelse 1? Øvelse 6 (valgfrit): Hvis du har en anden krystaloscillator, f.eks. En 4 MHz eller en 13,5 MHz eller hvad som helst, skal du ændre din 16 MHz -oscillator på dit brødbræt til det nye og se, hvordan det påvirker LED'ens blinkhastighed. Du skal nu kunne gennemgå den præcise beregning og forudsige præcis, hvordan det vil påvirke satsen.

Trin 9: Konklusion

Til jer, der har nået så langt, tillykke!

Jeg er klar over, at det er ret svært at slogge, når du læser mere og kigger op, end du kabler og eksperimenterer, men jeg håber, at du har lært følgende vigtige ting:

  1. Sådan fungerer programhukommelse
  2. Sådan fungerer SRAM
  3. Sådan slår du registre op
  4. Sådan finder du vejledning og ved, hvad de gør
  5. Sådan implementeres afbrydelser
  6. Hvordan CP udfører koden, hvordan SREG fungerer, og hvad der sker under afbrydelser
  7. Sådan laver du loops og spring og hopper rundt i koden
  8. Hvor vigtigt er det at læse databladet!
  9. Hvordan når du først ved, hvordan du gør alt dette for Atmega328p -mikrokontrolleren, vil det være en relativ kagevandring at lære nye controllere, som du er interesseret i.
  10. Sådan ændres CPU -tid til realtid og bruges i forsinkelsesrutiner.

Nu hvor vi har en masse teori ude af vejen, er vi i stand til at skrive bedre kode og kontrollere mere komplicerede ting. Så den næste vejledning gør vi netop det. Vi vil bygge et mere kompliceret, mere interessant kredsløb og styre det på sjove måder.

Øvelse 7: "Knæk" koden på forskellige måder og se, hvad der sker! Videnskabelig nysgerrighed baby! Nogen anden kan vaske opvasken rigtigt? Øvelse 8: Saml koden ved hjælp af "-l" -indstillingen for at generere en listefil. Dvs. "avra -l blink.lst blink.asm" og tag et kig på listefilen. Ekstra kredit: Den kode uden kommentarer, som jeg gav i begyndelsen, og den kommenterede kode, som vi diskuterer senere, er forskellige! Der er en linje med kode, der er anderledes. Kan du finde den? Hvorfor betyder den forskel ikke noget?

Håber du havde det sjovt! Vi ses næste gang …

Anbefalede: