AVR Assembler Övningsexempel 4 (6 / 9 steg)
Steg 6: Pekare och uppslagstabeller
Någon av er som har erfarenhet av att använda C eller C++ redan har erfarenhet av pekare. Vi kommer att använda samma sak här i samband med "uppslagstabeller".
Uppslagstabeller är ett annat sätt att compactifying vår kod att göra det kortare, elegantare och lättare att förstå.
Först kan skriva koden och sedan kommer vi att förklara vad som händer. Först, längst upp i vårt program får vi ett avsnitt som heter "siffror:" följt av vissa "DB" assembler direktiv. Dessa direktiv "definiera byte" och vad de gör är de rum de byte sekventiellt i ett visst avsnitt "Programminne" definieras av etikettnumren. Så att när hex koden är laddad på mikrokontroller, innehåller en viss del av flash-minne som lagrar alla program instruktionerna dessa byte en efter den andra i ordningen.
nummer:
DB 0b01111111, 0b11011110, 0b01011110, 0b11010010
DB 0b01010010, 0b11000000
Då kan vi faktiskt få dessa siffror när vi vill dem eftersom de alltid kommer att vara placerad på vissa angivna programmet minnesplatser. Kom ihåg hur vi behandlat avbrott? Vi placerade en instruktion på exakt 0x0020 i programminnet. Vi visste att om en timer overflow avbryta uppstod cpu skulle kontrollera den exakta platsen och utföra oavsett kommando vi lagt där. Bra uppslagstabeller fungerar på ett mycket liknande sätt.
Vi kommer att skriva om våra "dice:" märkt subrutin, som är den som berättar mikrokontroller vilket stift att slå på att få vilket nummer på en dö, så att i stället för en lång och ful i koden, den kan använda en loop och göra saker helt enkelt. Här är den nya koden:
Dice:
LDI ZH, high(2*numbers)
LDI ZL, low(2*numbers)
LDI temp, 0
Kolla:
inkl härda; Increment temp
CP die1, temp; Jämför die1 med 1
brne PC + 2. om inte lika inte ställa in die1
LPM die1, Z; Ladda die1 med nummer 1
CP die2, temp; Jämför die2 med 1
brne PC + 2. om inte lika inte ställa in die2
LPM die2, Z; Ladda die2 med nummer 1
CPI temp, 6. om temp klar 6 vi
breq PC + 3. om lika går till ret
adiw ZL, 1; öka till nästa nummer
rjmp kontroll. i uppslagstabellen
ser härda; Återställ temp
ret
Du ser att det är mycket kortare. I själva verket, när du befinner dig är upprepa samma uppsättning instruktioner om och om igen i en subrutin och enda som är olika varje gång att något visst antal är olika, det är ett perfekt tillfälle att använda en uppslagstabell. I de fall där du vill använda en "ögla" eller en "switch-fall" rutin i C eller några andra språk, som är en bra tid att använda en uppslagstabell i assembler.
Uppslagstabeller har rykte om sig att vara komplicerat men jag tror inte det är välförtjänt. Jag ska försöka att förklara det på ett enkelt sätt här.
Låt oss börja med atmega328p minne karta. Det finns tre olika typer av ledigt minne för att lagra saker. Den "programminne" som lagrar vårt program, den "SRAM dataminne" som innehåller alla de register som vi använder som allmänna syftet arbetar register, input/output portar, och alla de journaler som vi använder för att växla lite och kontroll som det är gjort, och slutligen "EEPROM" minne, som vi kommer att införa i en senare handledning (om jag förra det lång) och används för att lagra information som inte kommer att försvinna när vi slår av strömmen. Mycket användbart om du gör ett spel och du vill lagra somebodies poäng tills nästa gång de spelar!
Vi vet att varje byte av en viss typ av minne har en adress. Till exempel den första byten i koden vi utför är på 0x0000 och timer overflow avbrottshanterare är på 0x0020, etc. Du kommer att märka att eftersom vi har mer än 256 byte minne för i våra programminne utrymme inte kan vi bara använda adresser 0x00 upp till 0xFF. I själva verket har vi 32k flashminne i programmet minnesutrymme. Detta innebär att vi behöver adresser från 0x0000 upp till 0x7FFF.
Antag nu, att vi vill läsa vad som är på en viss adress i minnet? Till exempel när processorn blir ett bräddavlopp avbrott det går till 0x0020 och utför den instruktion som vi placerat där. Vad händer om vi vill placera instruktioner eller data eller vad på vissa specifika adress i programminne och sedan använda det i vårt program? Vi kan, förutom att våra generella registrerar endast rymmer 8 bitar (1 byte) mellan 0x00 och 0xFF, och som vi har sett, en adress har 2 byte att skriva ner (mellan 0x0000 och 0x7FFF). Så finns det inte tillräckligt med utrymme i ett allmänt register (dvs. en variabel som r16) att hålla en programminne adress. Vi kan inte säga "ldi r16, 0b0000000000000010" till exempel, eftersom R16 är inte tillräckligt stor. Så om vi har något sätt att lagra fullständiga adress hur kan vi gå dit under programmet? Vi kan bara plocka upp telefonen, ringa cpu och säga "kan du gå och köra vad vi lagras på 0x2a7f vänligen" du måste ha denna adress i ett r16 eller något och sedan "mov" det eller "out" det därifrån.
Så är här vad den ATmel folken har gjort för att lösa detta dilemma. De har dubbla utlovat några av våra generella register. Särskilt om man tittar på tabellen 7-2 på sidan 12 i databladet, kan du se hur de generella register är organiserade. Den registren r26, r27, r28, r29, r30 och r31 kan också kombineras till par kallas X, Y och Z. Så att X är r26 och r27 tillsammans, Y är r28 och r29 tillsammans, och Z är r30 och r31 tillsammans. Så om vi tar Z till exempel först hälften av det är r30 och andra hälften av det är r31. Så om vi vill lagra en programminne adress lagrar vi bara hälften av det i r30 och den andra hälften av det i r31 och sedan vi berätta cpu att leta upp Z om vi vill prata om hela tillsammans. De har genomfört två instruktioner som gör detta. Först är spm som står för "Store programminne" och den andra är lpm som står för "Load programminne". Så nu om vi vill få vad någonsin instruktion eller data lagras på minnesadress 0x24c8 till exempel, vi skulle sätta den adressen i r30 och r31 och sedan när vi vill få data skulle vi bara lpm det till en variabel genom att göra
LPM r16, Z
som kommer att gå till minnesadress Z ta oavsett data vi lagt där och sticka den i r16. Häftiga med detta är att om vi lägger till 1 till Z med hjälp
adiw Z, 1
då kommer Z nu "peka" nästa minnesadress efter den första. Så att om vi håller oss en hel lista med siffror i minnet en efter den andra vi kan gå igenom dem genom uppräkning Z.
Hur vi använda detta i vårt program?
Tja, eftersom varje nummer på tärningen visas genom att slå på och av olika hamnar som PC2 och PB5 vi lagra bara numret som gör detta för varje nummer på tärningen. Till exempel om vi "ut" 0b11010010 till PortC det kommer att ställa PC0 till 0, PB1 1, etc och slå på de motsvarande lysdioderna för att ge oss vårt nummer på tärningen. I detta fall nummer 4.
Så vi kommer att använda en "uppslagstabell" kallas "siffror:" att lagra alla dessa olika die konfigurationer och förenkla vår kod.
Jag tror att om du läser koden ovan, och leta upp olika instruktionerna i bruksanvisningen, kan du lätt räkna ut hur det fungerar. Bara konstig del är den första biten där vi initiera pekaren Z.
LDI ZH, high(2*numbers)
LDI ZL, low(2*numbers)
Vad detta innebär är initialiserar pekaren Z att peka på vår lista som heter "nummer". Anledningen till de 2 gånger framför är att vi vill ha adressen till "nummer" skiftat till vänster ett utrymme (det är vilka tider av två gör att binära tal). Detta lämnar gratis längst till höger lite (den minst signifikanta biten) som sedan används för att avgöra vilka byte programminne hänvisar vi till. Detta beror på att programmet minne är 2 byte (16 bitar) brett. Så till exempel i våra uppslagstabellen har vi två första siffror som
DB 0b01111111, 0b11011110
Eftersom programmet minnesutrymme är 16 bitar bred båda dessa nummer faktiskt kommer att sitta på samma programminne adress så det sättet ta vi den första eller den andra är därför vi behöver "times av 2" eller vänster SKIFT bitar. När den "minst signifikanta biten" av Z är 0 den pekar till den första av vår lista: 0b01111111, och när den minst signifikanta biten av Z är en 1 den kommer att peka på den andra ett på vår lista: 0b11011110.
Som ni kan se, ändras att lägga till 1 till Z den minst signifikanta bit från 0 till 1 och sedan lägga till 1 till Z igen steg Program minnen adress och LSB går tillbaka till en nolla. Så ser du att det fungerar bra för att plocka ut våra hela lista över lagrade nummer ett i taget genom att helt enkelt öka Z.
Märker att när vi flytta adressen till "nummer" lämnat genom att multiplicera med 2 till fri upp den minst signifikanta biten att använda för att välja den första eller andra byten lagras på den adressen vi förlorar den "mest signifikanta biten" i adressen. Detta innebär att vi bara kan lagra våra lookup tabelldata i adresser där den mest signifikanta biten spelar ingen roll - dvs alla våra namngiven data kommer att ha den samma mest signifikanta biten. Detta innebär att vår adress är effektivt 15 bitar lång. 2 ^ 15 är 32768 olika adresser tillgängliga för våra lagrade data. Vi ska titta på detta mer i detalj i nästa tutorial så oroa dig inte om det är lite förvirrande på denna punkt.
Nu vet du hur man använder uppslagstabeller och X, Y och Z pekare för att förenkla skrivandet kod.
Låt oss nu ge det fullständiga programmet med dessa innovationer ingår.