Inhoudsopgave:

AVR Assembler Tutorial 3: 9 stappen
AVR Assembler Tutorial 3: 9 stappen

Video: AVR Assembler Tutorial 3: 9 stappen

Video: AVR Assembler Tutorial 3: 9 stappen
Video: Ассемблер. Урок 3. Основы. Циклы 2024, Juli-
Anonim
AVR-assemblagehandleiding 3
AVR-assemblagehandleiding 3

Welkom bij les nummer 3!

Voordat we beginnen, wil ik een filosofisch punt maken. Wees niet bang om te experimenteren met de circuits en de code die we in deze tutorials aan het maken zijn. Verander draden, voeg nieuwe componenten toe, verwijder componenten, verander coderegels, voeg nieuwe regels toe, verwijder regels en kijk wat er gebeurt! Het is heel moeilijk om iets te breken en als je dat doet, wat maakt het uit? Niets dat we gebruiken, inclusief de microcontroller, is erg duur en het is altijd leerzaam om te zien hoe dingen kunnen mislukken. U zult niet alleen ontdekken wat u de volgende keer niet moet doen, maar, nog belangrijker, u weet ook waarom u het niet moet doen. Als je op mij lijkt, toen je een kind was en je een nieuw speeltje kreeg, duurde het niet lang voordat je het in stukken had om te zien wat het goed maakte? Soms raakte het speelgoed onherstelbaar beschadigd, maar dat was niet erg. Door een kind zijn nieuwsgierigheid te laten ontdekken, zelfs tot op het punt van kapot speelgoed, wordt hij een wetenschapper of een ingenieur in plaats van een vaatwasser.

Vandaag gaan we een heel eenvoudig circuit bedraden en dan een beetje zwaar in de theorie worden. Sorry hiervoor, maar we hebben de tools nodig! Ik beloof dat we dit goed zullen maken in tutorial 4, waar we wat serieuzere circuits gaan bouwen en het resultaat zal best gaaf zijn. De manier waarop u al deze tutorials moet doen, is echter op een zeer langzame, contemplatieve manier. Als je er gewoon doorheen ploegt, het circuit bouwt, de code kopieert en plakt, en het dan uitvoert, zal het zeker werken, maar je leert er niets van. Over elke regel moet je nadenken. Pauze. Experiment. Uitvinden. Als je het op die manier doet, ben je aan het einde van de 5e tutorial klaar met het bouwen van coole dingen en heb je geen bijles meer nodig. Anders ben je gewoon aan het kijken in plaats van aan het leren en creëren.

In ieder geval genoeg filosofie, laten we maar beginnen!

In deze tutorial heb je nodig:

  1. jouw prototypebord
  2. een LED
  3. aansluitdraden
  4. een weerstand rond 220 tot 330 ohm
  5. De gebruiksaanwijzing: www.atmel.com/images/atmel-0856-avr-instruction-se…
  6. Het gegevensblad: www.atmel.com/images/Atmel-8271-8-bit-AVR-Microco…
  7. een andere kristaloscillator (optioneel)

Hier is een link naar de volledige verzameling tutorials:

Stap 1: Het circuit bouwen

Het circuit bouwen
Het circuit bouwen

Het circuit in deze tutorial is uiterst eenvoudig. We gaan in wezen het "knipper"-programma schrijven, dus alles wat we nodig hebben is het volgende.

Sluit een LED aan op PD4, vervolgens op een weerstand van 330 ohm en vervolgens op aarde. d.w.z.

PD4 - LED - R(330) - GND

en dat is het!

De theorie zal echter moeilijk worden…

Stap 2: Waarom hebben we de opmerkingen en het M328Pdef.inc-bestand nodig?

Ik denk dat we moeten beginnen met te laten zien waarom het include-bestand en de opmerkingen nuttig zijn. Geen van hen is echt nodig en je kunt zonder code op dezelfde manier schrijven, samenstellen en uploaden en het zal perfect werken (hoewel je zonder het include-bestand wat klachten kunt krijgen van de assembler -- maar geen fouten)

Hier is de code die we vandaag gaan schrijven, behalve dat ik de opmerkingen en het include-bestand heb verwijderd:

.apparaat ATmega328P

.org 0x0000 jmp a.org 0x0020 jmp ea: ldi r16, 0x05 uit 0x25, r16 ldi r16, 0x01 sts 0x6e, r16 sei clr r16 uit 0x26, r16 sbi 0x0a, 0x04 sbi 0x0b, 0x04 b0: sbi 0x0b, 0x04 b0 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

vrij simpel toch? Hahaha. Als je dit bestand hebt samengesteld en geüpload, zal de LED knipperen met een snelheid van 1 keer knipperen per seconde, waarbij het knipperen 1/2 seconde duurt en de pauze tussen het knipperen 1/2 seconde.

Het is echter nauwelijks verhelderend om naar deze code te kijken. Als je code als deze zou schrijven en je zou het in de toekomst willen wijzigen of hergebruiken, zou je het moeilijk hebben.

Dus laten we de opmerkingen plaatsen en het bestand weer opnemen, zodat we er een idee van kunnen krijgen.

Stap 3: Blink.asm

Hier is de code die we vandaag zullen bespreken:

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

; geschreven door: 1o_o7; datum:; versie: 1.0; bestand opgeslagen als: blink.asm; voor AVR: atmega328p; klokfrequentie: 16MHz (optioneel);****************************************; Programmafunctie: ---------------------; telt seconden af door een LED te laten knipperen;; PD4 - LED - R (330 ohm) - GND;;--------------------------------------.nolist.include "./m328Pdef.inc".lijst;==============; Verklaringen:.def temp = r16.def overlopen = r17.org 0x0000; geheugen (PC) locatie van reset-handler rjmp Reset; jmp kost 2 cpu-cycli en rjmp kost slechts 1; dus tenzij je meer dan 8k bytes moet springen; je hebt alleen rjmp nodig. Sommige microcontrollers daarom alleen; hebben rjmp en niet jmp.org 0x0020; geheugenlocatie van Timer0 overloophandler rjmp overflow_handler; ga hierheen als een timer0-overlooponderbreking optreedt;============ Reset: ldi temp, 0b00000101 uit TCCR0B, temp; stel de klokkiezerbits CS00, CS01, CS02 in op 101; dit zet Timer Counter0, TCNT0 in de FCPU/1024-modus; dus het tikt op de CPU freq/1024 ldi temp, 0b00000001 sts TIMSK0, temp; stel de Timer Overflow Interrupt Enable (TOIE0) bit in; van het Timer Interrupt Mask Register (TIMSK0) sei; globale interrupts inschakelen -- gelijk aan "sbi SREG, I" clr temp out TCNT0, temp; initialiseer de Timer/Teller op 0 sbi DDRD, 4; stel PD4 in op uitgang;======================; Hoofdgedeelte van het programma: knipperen: sbi PORTD, 4; zet LED aan PD4 rcall vertraging; vertraging is 1/2 seconde cbi PORTD, 4; schakel LED uit PD4 rcall vertraging; vertraging zal 1/2 seconde rjmp knipperen; loop terug naar de startvertraging: clr loopt over; stel overlopen in op 0 sec_count: cpi overlopen, 30; vergelijk het aantal overlopen en 30 brne sec_count; branch to back to sec_count indien niet gelijk aan ret; als er 30 overlopen zijn opgetreden, keer terug naar knipperen overflow_handler: inc overflows; voeg 1 toe aan de overflows variabele cpi overflows, 61; vergelijk met 61 brne PC+2; Programmateller + 2 (volgende regel overslaan) indien niet gelijk aan clr overloopt; als 61 overlopen zijn opgetreden, zet u de teller terug op nul reti; terugkeer van interrupt

Zoals je kunt zien, zijn mijn opmerkingen nu wat beknopter. Als we eenmaal weten wat de commando's in de instructieset doen, hoeven we dat niet uit te leggen in opmerkingen. We hoeven alleen maar uit te leggen wat er aan de hand is vanuit het oogpunt van het programma.

We zullen stuk voor stuk bespreken wat dit allemaal doet, maar laten we eerst proberen een globaal perspectief te krijgen. Het hoofdgedeelte van het programma werkt als volgt.

Eerst stellen we bit 4 van PORTD in met "sbi PORTD, 4", dit stuurt een 1 naar PD4 die de spanning op 5V op die pin zet. Hierdoor gaat de LED aan. We springen dan naar de subroutine "vertraging" die een halve seconde telt (we zullen later uitleggen hoe het dit doet). We keren dan terug naar het knipperen en wissen van bit 4 op PORTD die PD4 instelt op 0V en dus de LED uitschakelt. We vertragen dan nog een 1/2 seconde en springen dan weer terug naar het begin van knipperen met "rjmp blink".

U moet deze code uitvoeren en zien dat het doet wat het zou moeten doen.

En daar heb je het! Dat is alles wat deze code fysiek doet. De interne mechanica van wat de microcontroller doet, is een beetje meer betrokken en daarom doen we deze tutorial. Laten we daarom elke sectie achtereenvolgens bespreken.

Stap 4:.org Assembler-richtlijnen

We weten al wat de.nolist,.list,.include en.def assembler-richtlijnen doen uit onze vorige tutorials, dus laten we eerst eens kijken naar de 4 regels code die daarna komen:

.org 0x0000

jmp Reset.org 0x0020 jmp overflow_handler

Het.org-statement vertelt de assembler waar in "Program Memory" het volgende statement moet worden geplaatst. Terwijl uw programma wordt uitgevoerd, bevat de "Program Counter" (afgekort als PC) het adres van de huidige regel die wordt uitgevoerd. Dus in dit geval, wanneer de pc op 0x0000 staat, ziet hij de opdracht "jmp Reset" op die geheugenlocatie. De reden dat we jmp Reset op die locatie willen plaatsen, is omdat wanneer het programma begint of de chip wordt gereset, de pc op deze plek begint met het uitvoeren van code. Dus, zoals we kunnen zien, hebben we het zojuist verteld om onmiddellijk naar het gedeelte met het label "Reset" te "springen". Waarom deden we dat? Dat betekent dat de laatste twee regels hierboven gewoon worden overgeslagen! Waarom?

Nou, dat is waar dingen interessant worden. Je zult nu een pdf-viewer moeten openen met de volledige ATmega328p-datasheet waar ik naar verwees op de eerste pagina van deze tutorial (daarom is dit item 4 in de sectie "die je nodig hebt"). Als je scherm te klein is, of je hebt al veel te veel vensters open (zoals bij mij het geval is), kun je doen wat ik doe en het op een Ereader of je Android-telefoon zetten. Je zult het de hele tijd gebruiken als je van plan bent om assembly-code te schrijven. Het leuke is dat alle microcontrollers op zeer vergelijkbare manieren zijn georganiseerd en dus als je eenmaal gewend bent aan het lezen van datasheets en het coderen ervan, zul je het bijna triviaal vinden om hetzelfde te doen voor een andere microcontroller. We leren dus eigenlijk hoe we alle microcontrollers in zekere zin kunnen gebruiken en niet alleen de atmega328p.

Oké, ga naar pagina 18 in de datasheet en bekijk figuur 8.2.

Zo is het programmageheugen in de microcontroller ingesteld. Je kunt zien dat het begint met adres 0x0000 en is opgedeeld in twee secties; een applicatie-flitsgedeelte en een opstartflitsgedeelte. Als u kort naar pagina 277 tabel 27-14 verwijst, zult u zien dat de toepassingsflitssectie de locaties van 0x0000 tot 0x37FF in beslag neemt en de opstartflitssectie de overige locaties van 0x3800 tot 0x3FFF inneemt.

Oefening 1: Hoeveel locaties zijn er in het programmageheugen? D.w.z. converteer 3FFF naar decimaal en voeg 1 toe aangezien we beginnen te tellen bij 0. Aangezien elke geheugenlocatie 16 bits (of 2 bytes) breed is, wat is dan het totale aantal bytes geheugen? Converteer dit nu naar kilobytes, onthoud dat er 2 ^ 10 = 1024 bytes in een kilobyte zitten. Het opstartflitsgedeelte gaat van 0x3800 naar 0x37FF, hoeveel kilobytes is dit? Hoeveel kilobytes aan geheugen kunnen we nog gebruiken om ons programma op te slaan? Met andere woorden, hoe groot kan ons programma zijn? Tot slot, hoeveel regels code kunnen we hebben?

Oké, nu we alles weten over de organisatie van het flash-programmageheugen, gaan we verder met onze bespreking van de.org-statements. We zien dat de eerste geheugenlocatie 0x0000 onze instructie bevat om naar onze sectie te gaan die we Reset noemden. Nu zien we wat de instructie ".org 0x0020" doet. Er staat dat we willen dat de instructie op de volgende regel op geheugenlocatie 0x0020 wordt geplaatst. De instructie die we daar hebben geplaatst, is een sprong naar een sectie in onze code die we "overflow_handler" hebben genoemd … waarom zouden we nu in vredesnaam eisen dat deze sprong op geheugenlocatie 0x0020 wordt geplaatst? Om daar achter te komen, gaan we naar pagina 65 in de datasheet en bekijken we Tabel 12-6.

Tabel 12-6 is een tabel met "Reset- en Interrupt-vectoren" en deze laat precies zien waar de pc naartoe gaat als hij een "interrupt" ontvangt. Als u bijvoorbeeld naar Vector nummer 1 kijkt. De "bron" van de onderbreking is "RESET", wat wordt gedefinieerd als "Externe pin, Power-on Reset, Brown-out Reset en Watchdog-systeemreset", wat betekent dat als een van de die dingen gebeuren met onze microcontroller, de pc begint ons programma uit te voeren op programmageheugenlocatie 0x0000. Hoe zit het dan met onze.org-richtlijn? Welnu, we hebben een opdracht op geheugenlocatie 0x0020 geplaatst en als je naar beneden kijkt in de tabel, zul je zien dat als er een Timer/Counter0-overloop plaatsvindt (afkomstig van TIMER0 OVF), het alles zal uitvoeren wat zich op locatie 0x0020 bevindt. Dus wanneer dat gebeurt, springt de pc naar de plek die we "overflow_handler" hebben genoemd. Cool toch? Je zult zo zien waarom we dit hebben gedaan, maar laten we eerst deze stap van de tutorial afronden met een terzijde.

Als we onze code netter willen maken, moeten we de 4 regels die we momenteel bespreken echt vervangen door de volgende (zie pagina 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; PC = 0x0032

Dus als een bepaalde interrupt optreedt, zal deze gewoon "reti" zijn, wat betekent "terugkeren van interrupt" en er gebeurt niets anders. Maar als we deze verschillende interrupts nooit "inschakelen", dan zullen ze niet worden gebruikt en kunnen we programmacode op deze plekken plaatsen. In ons huidige "blink.asm"-programma gaan we alleen de timer0 overflow-interrupt inschakelen (en natuurlijk de reset-interrupt die altijd is ingeschakeld) en dus zullen we ons niet druk maken over de anderen.

Hoe kunnen we dan de timer0 overlooponderbreking "inschakelen"? … dat is het onderwerp van onze volgende stap in deze tutorial.

Stap 5: Timer/Teller 0

Timer/Teller 0
Timer/Teller 0

Kijk eens naar de bovenstaande foto. Dit is het besluitvormingsproces van de "pc" wanneer een externe invloed de stroom van ons programma "onderbreekt". Het eerste dat het doet wanneer het een signaal van buitenaf krijgt dat er een interrupt is opgetreden, is dat het controleert of we het "interrupt enable"-bit voor dat type interrupt hebben ingesteld. Als dat niet het geval is, gaat het gewoon door met het uitvoeren van onze volgende regel code. Als we dat specifieke interrupt-activeringsbit hebben ingesteld (zodat er een 1 op die bitlocatie staat in plaats van een 0), zal het dan controleren of we "global interrupts" hebben ingeschakeld, zo niet, dan gaat het opnieuw naar de volgende regel van de code en ga verder. Als we ook globale interrupts hebben ingeschakeld, gaat het naar de programmageheugenlocatie van dat type interrupt (zoals weergegeven in Tabel 12-6) en voert het de opdracht uit die we daar hebben geplaatst. Laten we dus eens kijken hoe we dit allemaal in onze code hebben geïmplementeerd.

Het gedeelte met het label Reset van onze code begint met de volgende twee regels:

Resetten:

ldi temp, 0b00000101 uit TCCR0B, temp

Zoals we al weten, laadt dit in temp (d.w.z. R16) het nummer dat onmiddellijk volgt, namelijk 0b00000101. Vervolgens schrijft het dit nummer naar het register met de naam TCCR0B met behulp van de opdracht "out". Wat is dit register? Laten we naar pagina 614 van de datasheet gaan. Dit staat in het midden van een tabel met een overzicht van alle registers. Op adres 0x25 vind je TCCR0B. (Nu weet je waar de regel "out 0x25, r16" vandaan kwam in mijn niet-becommentarieerde versie van de code). We zien aan het bovenstaande codesegment dat we de 0e bit en de 2e bit hebben ingesteld en de rest hebben gewist. Door naar de tabel te kijken, kunt u zien dat dit betekent dat we CS00 en CS02 hebben ingesteld. Laten we nu naar het hoofdstuk in de datasheet gaan met de naam "8-bit Timer/Counter0 with PWM". Ga vooral naar pagina 107 van dat hoofdstuk. U ziet dezelfde beschrijving van het "Timer/Counter Control Register B" (TCCR0B)-register dat we net zagen in de overzichtstabel van het register (dus we hadden hier meteen kunnen komen, maar ik wilde dat u zou zien hoe u de samenvattende tabellen moet gebruiken voor toekomstige referentie). De datasheet blijft een beschrijving geven van elk van de bits in dat register en wat ze doen. We zullen dat allemaal voor nu overslaan en de pagina omslaan naar Tabel 15-9. Deze tabel toont de "Clock Select Bit Beschrijving". Kijk nu in die tabel totdat je de regel vindt die overeenkomt met de bits die we zojuist in dat register hebben gezet. De regel zegt "clk/1024 (van prescaler)". Wat dit betekent is dat we willen dat Timer/Counter0 (TCNT0) meegaat met een snelheid die de CPU-frequentie is gedeeld door 1024. Aangezien we onze microcontroller hebben gevoed door een 16MHz kristaloscillator, betekent dit dat de snelheid waarmee onze CPU instructies uitvoert is 16 miljoen instructies per seconde. Dus de snelheid die onze TCNT0-teller tikt, is dan 16 miljoen/1024 = 15625 keer per seconde (probeer het met verschillende klokselectiebits en kijk wat er gebeurt - onthoud onze filosofie?). Laten we het nummer 15625 in ons achterhoofd houden voor later en verder gaan met de volgende twee regels code:

ldi-temp, 0b00000001

st TIMSK0, temp

Dit stelt de 0e bit van een register met de naam TIMSK0 in en wist de rest. Als u pagina 109 in de datasheet bekijkt, ziet u dat TIMSK0 staat voor "Timer/Counter Interrupt Mask Register 0" en dat onze code de 0e bit heeft ingesteld met de naam TOIE0 wat staat voor "Timer/Counter0 Overflow Interrupt Enable" … Daar! Nu zie je waar dit allemaal over gaat. We hebben nu de "interrupt enable bit set" zoals we wilden vanaf de eerste beslissing in onze afbeelding bovenaan. Dus nu hoeven we alleen maar "global interrupts" in te schakelen en ons programma zal in staat zijn om op dit soort interrupts te reageren. We zullen binnenkort globale interrupts inschakelen, maar voordat we dat doen, ben je misschien door iets in de war geweest.. waarom heb ik in godsnaam het commando "sts" gebruikt om naar het TIMSK0-register te kopiëren in plaats van de gebruikelijke "out"?

Wanneer je me een instructie ziet gebruiken die je nog niet eerder hebt gezien, moet je eerst naar pagina 616 in de datasheet gaan. Dit is de "Samenvatting van de instructieset". Zoek nu de instructie "STS", die ik heb gebruikt. Er staat dat het een nummer uit een R-register nodig heeft (we gebruikten R16) en "Direct opslaan naar SRAM"-locatie k (in ons geval gegeven door TIMSK0). Dus waarom moesten we "sts" gebruiken, wat 2 klokcycli kost (zie laatste kolom in tabel) om op te slaan in TIMSK0 en we hadden alleen "out" nodig, wat slechts één klokcyclus kost, om eerder in TCCR0B op te slaan? Om deze vraag te beantwoorden, moeten we teruggaan naar onze registeroverzichtstabel op pagina 614. U ziet dat het TCCR0B-register op adres 0x25 staat maar ook op (0x45) toch? Dit betekent dat het een register in SRAM is, maar het is ook een bepaald type register dat een "poort" (of i/o-register) wordt genoemd. Als je naar de instructie-overzichtstabel kijkt naast het "out"-commando, zul je zien dat het waarden uit de "werkregisters" zoals R16 neemt en deze naar een PORT stuurt. We kunnen dus "out" gebruiken bij het schrijven naar TCCR0B en onszelf een klokcyclus besparen. Maar zoek nu TIMSK0 op in de registertabel. Je ziet dat het adres 0x6e heeft. Dit valt buiten het bereik van poorten (die alleen de eerste 0x3F-locaties van SRAM zijn) en dus moet je terugvallen op het gebruik van het sts-commando en twee CPU-klokcycli nemen om het te doen. Lees op dit moment opmerking 4 aan het einde van de instructie-overzichtstabel op pagina 615. Merk ook op dat al onze invoer- en uitvoerpoorten, zoals PORTD, zich onderaan de tabel bevinden. PD4 is bijvoorbeeld bit 4 op adres 0x0b (nu zie je waar alle 0x0b-dingen vandaan kwamen in mijn niet-gecommentarieerde code!).. oke, snelle vraag: heb je de "sts" gewijzigd in "out" en kijk wat gebeurt? Onthoud onze filosofie! Maak het stuk! geloof me niet zomaar op mijn woord.

Oké, voordat we verder gaan, ga even naar pagina 19 in de datasheet. U ziet een afbeelding van het datageheugen (SRAM). De eerste 32 registers in SRAM (van 0x0000 tot 0x001F) zijn de "werkregisters voor algemene doeleinden" R0 tot en met R31 die we altijd als variabelen in onze code gebruiken. De volgende 64 registers zijn de I/O-poorten tot 0x005f (dwz degene waar we het over hadden en die adressen zonder haakjes ernaast hebben in de registertabel die we kunnen gebruiken met de opdracht "out" in plaats van "sts"). het volgende gedeelte van SRAM bevat alle andere registers in de overzichtstabel tot adres 0x00FF, en ten slotte is de rest interne SRAM. Laten we nu snel naar pagina 12 gaan. Daar zie je een tabel met de "werkregisters voor algemene doeleinden" die we altijd als onze variabelen gebruiken. Zie je de dikke lijn tussen de nummers R0 tot R15 en dan R16 tot R31? Die regel is de reden waarom we R16 altijd als de kleinste gebruiken en ik zal er wat meer op ingaan in de volgende tutorial, waar we ook de drie 16-bits indirecte adresregisters, X, Y en Z, nodig zullen hebben. ga daar nog even op in, want we hebben het nu niet nodig en we lopen hier al genoeg vast.

Sla één pagina terug naar pagina 11 van de datasheet. U ziet rechtsboven een schema van het SREG-register? Je ziet dat bit 7 van dat register "I" wordt genoemd. Ga nu naar beneden en lees de beschrijving van Bit 7…. ja! Het is het Global Interrupt Enable-bit. Dat is wat we moeten instellen om door de tweede beslissing in ons diagram hierboven te gaan en timer/counter-overlooponderbrekingen in ons programma toe te staan. Dus de volgende regel van ons programma zou moeten luiden:

sbi SREG, I

die de bit met de naam "I" in het SREG-register instelt. In plaats daarvan hebben we echter de instructie gebruikt:

sei

in plaats daarvan. Dit bit wordt zo vaak in programma's ingesteld dat ze het gewoon eenvoudiger hebben gemaakt.

Oke! Nu hebben we de overflow-interrupts klaar voor gebruik, zodat onze "jmp overflow_handler" wordt uitgevoerd wanneer er een optreedt.

Kijk voordat we verder gaan nog even in het SREG-register (Status Register) want dat is erg belangrijk. Lees wat elk van de vlaggen vertegenwoordigt. In het bijzonder zullen veel van de instructies die we gebruiken deze vlaggen de hele tijd instellen en controleren. Later zullen we bijvoorbeeld het commando "CPI" gebruiken, wat "vergelijk onmiddellijk" betekent. Bekijk de instructieoverzichtstabel voor deze instructie en merk op hoeveel vlaggen het in de kolom "vlaggen" zet. Dit zijn allemaal vlaggen in SREG en onze code zal ze instellen en constant controleren. Binnenkort ziet u voorbeelden. Eindelijk is het laatste stukje van dit gedeelte van de code:

clr temp

uit TCNT0, temp sbi DDRD, 4

De laatste regel is hier vrij duidelijk. Het stelt gewoon het 4e bit van het Data Direction Register voor PortD in, waardoor PD4 OUTPUT wordt.

De eerste stelt de variabele temp in op nul en kopieert die vervolgens naar het TCNT0-register. TCNT0 is onze Timer/Teller0. Dit zet het op nul. Zodra de pc deze regel uitvoert, begint de timer0 bij nul en telt met een snelheid van 15625 keer per seconde. Het probleem is dit: TCNT0 is toch een "8-bit" register? Dus wat is het grootste getal dat een 8-bits register kan bevatten? Nou, 0b11111111 is het. Dit is het getal 0xFF. Dat is 255. Dus je ziet wat er gebeurt? De timer raast voort en neemt 15625 keer per seconde toe en elke keer dat hij 255 bereikt, loopt hij "over" en gaat weer terug naar 0. Op hetzelfde moment dat het teruggaat naar nul, zendt het een Timer Overflow Interrupt-signaal uit. De pc krijgt dit en je weet wat het nu doet toch? Ja. Het gaat naar de programmeergeheugenlocatie 0x0020 en voert de instructie uit die het daar vindt.

Super goed! Als je nog steeds bij me bent, ben je een onvermoeibare superheld! Laten we door gaan…

Stap 6: Overloophandler

Laten we dus aannemen dat het timer/counter0-register zojuist is overgelopen. We weten nu dat het programma een interruptsignaal ontvangt en 0x0020 uitvoert, wat de programmateller, pc vertelt om naar het label "overflow_handler" te springen, het volgende is de code die we na dat label hebben geschreven:

overflow_handler:

incl. overlopen cpi overlopen, 61 brne PC+2 clr overlopen reti

Het eerste wat het doet is de variabele "overflows" verhogen (wat onze naam is voor werkregister R17 voor algemene doeleinden) en vervolgens "vergelijkt" het de inhoud van overflows met het getal 61. De manier waarop de instructie cpi werkt, is dat het eenvoudigweg aftrekt de twee getallen en als het resultaat nul is, wordt de Z-vlag in het SREG-register gezet (ik zei toch dat we dit register de hele tijd zouden zien). Als de twee getallen gelijk zijn, is de Z-vlag een 1, als de twee getallen niet gelijk zijn, is het een 0.

De volgende regel zegt "brne PC+2" wat "tak indien niet gelijk" betekent. In wezen controleert het de Z-vlag in SREG en als het GEEN één is (dwz de twee getallen zijn niet gelijk, als ze gelijk waren, zou de nulvlag worden ingesteld) vertakt de pc naar PC+2, wat betekent dat het de volgende overslaat regel en gaat rechtstreeks naar "reti", die terugkeert van de interrupt naar de plaats in de code toen de interrupt arriveerde. Als de brne-instructie een 1 in het nulvlagbit zou vinden, zou deze niet vertakken en in plaats daarvan gewoon doorgaan naar de volgende regel die zou overlopen en deze zou resetten naar 0.

Wat is het netto resultaat van dit alles?

Welnu, we zien dat elke keer dat er een timeroverloop is, deze handler de waarde van "overflows" met één verhoogt. Dus de variabele "overlopen" telt het aantal overlopen als ze optreden. Telkens wanneer het nummer 61 bereikt, stellen we het opnieuw in op nul.

Waarom zouden we dat in hemelsnaam doen?

Laten we zien. Bedenk dat onze kloksnelheid voor onze CPU 16 MHz is en dat we deze "voorgeschaald" hebben met TCCR0B, zodat de timer alleen telt met een snelheid van 15625 tellingen per seconde, toch? En elke keer dat de timer een telling van 255 bereikt, loopt hij over. Dus dat betekent dat het 15625/256 = 61,04 keer per seconde overstroomt. We houden het aantal overflows bij met onze variabele "overflows" en we vergelijken dat aantal met 61. We zien dus dat "overflows" elke seconde gelijk zijn aan 61! Dus onze handler zal "overflows" elke seconde op nul resetten. Dus als we gewoon de variabele "overflows" zouden monitoren en nota zouden nemen van elke keer dat deze naar nul wordt gereset, zouden we seconde per seconde in realtime tellen (Merk op dat we in de volgende tutorial zullen laten zien hoe je een meer exacte vertraging in milliseconden op dezelfde manier als de Arduino "vertraging" routine werkt).

Nu hebben we de overlooponderbrekingen van de timer "behandeld". Zorg ervoor dat u begrijpt hoe dit werkt en ga dan verder met de volgende stap waarin we gebruik maken van dit feit.

Stap 7: Vertraging

Nu we hebben gezien dat onze timer overflow interrupt handler "overflow_handler" routine de variabele "overflows" eenmaal per seconde op nul zal zetten, kunnen we dit feit gebruiken om een "delay" subroutine te ontwerpen.

Bekijk de volgende code van onder onze vertraging: label

vertraging:

clr loopt over sec_count: cpi loopt over, 30 brne sec_count ret

We gaan deze subroutine aanroepen elke keer dat we een vertraging in ons programma nodig hebben. De manier waarop het werkt, is dat het eerst de variabele "overflows" op nul zet. Dan komt het in een gebied met het label "sec_count" en vergelijkt overlopen met 30, als ze niet gelijk zijn, vertakt het terug naar het label sec_count en vergelijkt opnieuw, en opnieuw, enz. totdat ze uiteindelijk gelijk zijn (onthoud dat de hele tijd dit gaat op onze timer blijft de interrupt-handler de variabele overflows verhogen en dus verandert deze elke keer dat we hier rondlopen. Wanneer overflows uiteindelijk gelijk zijn aan 30, komt het uit de lus en keert terug naar waar we vertraging hebben genoemd: van. Het netto resultaat is een vertraging van 1/2 seconde

Oefening 2: Wijzig de routine overflow_handler in het volgende:

overflow_handler:

incl. overloopt reti

en voer het programma uit. Is er iets anders? Waarom of waarom niet?

Stap 8: Knipper

Laten we tot slot eens kijken naar de knipperroutine:

knipperen:

sbi PORTD, 4 rcall vertraging cbi PORTD, 4 rcall vertraging rjmp knipperen

Eerst zetten we PD4 aan, dan roepen we onze vertragingssubroutine op. We gebruiken rcall zodat wanneer de pc bij een "ret"-statement komt, hij terugkomt naar de regel die volgt op rcall. Dan vertraagt de vertragingsroutine 30 tellen in de overloopvariabele zoals we hebben gezien en dit is bijna precies 1/2 seconde, dan zetten we PD4 uit, vertragen nog een 1/2 seconde en gaan dan weer terug naar het begin.

Het netto resultaat is een knipperende LED!

Ik denk dat je het er nu mee eens bent dat "blink" waarschijnlijk niet het beste "hello world"-programma in assembler is.

Oefening 3: Verander de verschillende parameters in het programma zodat de LED met verschillende snelheden knippert, zoals een seconde of 4 keer per seconde, enz. Oefening 4: Verander deze zodat de LED verschillende tijdsduur aan en uit is. Bijvoorbeeld 1/4 seconde aan en dan 2 seconden uit of iets dergelijks. Oefening 5: Verander de TCCR0B clock select bits naar 100 en ga dan verder de tabel op. Op welk punt wordt het niet meer te onderscheiden van ons "hello.asm"-programma uit tutorial 1? Oefening 6 (optioneel): Als je een andere kristaloscillator hebt, zoals een 4 MHz of een 13,5 MHz of wat dan ook, vervang dan je 16 MHz-oscillator op je breadboard voor de nieuwe en kijk hoe dat de knippersnelheid van de LED beïnvloedt. U zou nu in staat moeten zijn om de precieze berekening te doorlopen en precies te voorspellen hoe dit het tarief zal beïnvloeden.

Stap 9: Conclusie

Voor degenen onder jullie die het zo ver hebben gehaald, gefeliciteerd!

Ik realiseer me dat het behoorlijk moeilijk is om te ploeteren als je meer aan het lezen en opzoeken bent dan aan het bedraden en experimenteren, maar ik hoop dat je de volgende belangrijke dingen hebt geleerd:

  1. Hoe het programmageheugen werkt
  2. Hoe SRAM werkt
  3. Registers opzoeken
  4. Instructies opzoeken en weten wat ze doen
  5. Interrupts implementeren?
  6. Hoe de CP de code uitvoert, hoe de SREG werkt en wat er gebeurt tijdens interrupts
  7. Hoe loops en sprongen te doen en rond te stuiteren in de code
  8. Hoe belangrijk is het om de datasheet te lezen!
  9. Als je eenmaal weet hoe je dit alles voor de Atmega328p-microcontroller moet doen, zal het een relatieve makkie zijn om nieuwe controllers te leren waarin je geïnteresseerd bent.
  10. Hoe u de CPU-tijd in realtime kunt veranderen en deze kunt gebruiken in vertragingsroutines.

Nu we veel theorie uit de weg hebben, zijn we in staat om betere code te schrijven en ingewikkeldere dingen te beheersen. Dus de volgende tutorial zullen we precies dat doen. We zullen een ingewikkelder, interessanter circuit bouwen en het op leuke manieren besturen.

Oefening 7: "Breek" de code op verschillende manieren en kijk wat er gebeurt! Wetenschappelijke nieuwsgierigheid schat! Iemand anders kan toch de afwas doen? Oefening 8: Stel de code samen met de optie "-l" om een lijstbestand te genereren. D.w.z. "avra -l blink.lst blink.asm" en bekijk het lijstbestand. Extra tegoed: de code zonder commentaar die ik aan het begin heb gegeven en de code met commentaar die we later bespreken, verschillen! Er is één regel code die anders is. Kan je het vinden? Waarom maakt dat verschil niet uit?

Hopelijk heb je lol gehad! Tot de volgende keer…

Aanbevolen: