CT3760 Mikrodatorteknik Föreläsning 4 Tisdag 2005-09-06 Stacken I datasammmanhang är en stack ett minnesområde. Det är processorn som använder stacken. För att skapa en stack anger man en adress i stackpekarregistret. Detta register finns i dataminnet på adress 0x005D och 0x005E. Registret kan nås i I/O-delen med hjälp av adressserna 0x3D och 0x3E. Dataminne I/O Stack 0x005D 0x3D SP low byte 0x005E 0x3E SP high byte Stacken bör om möjligt placeras i det interna SRAM-minnet. Detta för att så snabb tillgång till stacken. Att undersöka: Var hamnar stacken då man i assemblerprogrammering använder.include m16def.inc? I programmen hittills har vi skrivit: START: LDI R16, low(ramend) OUT SPL, R16 LDI R16, high(ramend) OUT SPH, R16 Filen m16def.inc innehåller en hel del informtion och förkortningar. Filen kan sökas under katalogen C:\Program Files\Atmel\ Den kan läsas i Notepad, men ännu bäättre i Programmers Notepad. Där hittar man följande:.equ RAMEND = $45F Detta är en minnesadress. Adressen till den sista tillgängliga minnesbyten i RAM. Adresser är 16 bitar. Data är 8 bitar. Stackpekaren pekar på en adress, alltså måste den vara på 16 bitar. Pekaren är uppdelad i två delar. SPL och SPH. Dessa två tillsammans utgör stackpekaren. Man hämtar först den låga delen av RAMEND, mellanlagrar i R16 och skriver sedan till SPL. Hämtar sedan den höga delen. Nu har vi en pekare som pekar på den högsta tillgängliga platsen i minnet. Stacken växer man man lägger något till stacken. Stacken växer mot lägre adresser, därför är det lämpligt att stacken ligger längst upp dataminnet. 2005-09-08 H:\CT3760\Period1_2005\F4.doc 1/6
Stackpekaren pekar alltid på nästa lediga plats. Så här ser det ut innan stacken används. Dataminne Sackpekare SRAM SPH SPL Högsta adressen Antag att innehållet i register R16 skall skickas till stacken. Instruktionen som används är R16 Innehållet i R16 kopieras till den plats som anges i stackpekarregistret, när kopieringen är klar minskas innehållet i stackpekaren med ett. Stackpekaren pekar på nästa lediga plats. Så här ser instruktionen ut: Push Register on Stack Description: This instruction stores the contents of register Rr on the STACK. The Stack Pointer is postdecremented by 1 after the. This instruction is not available in all devices. Refer to the device specific instruction set summary. Operation: STACK Rr Syntax: Operands: Program Counter: Stack: Rr 0 r 31 PC PC + 1 SP SP - 1 16-bit Opcode: 1001 001d dddd 1111 Stausregistret påverkas ej av operationen. Exempel: Vi har ett program där stackpekaren är definierad. Någonstans i programmet finns följande rader: R0 R1 R2 R3 2005-09-08 H:\CT3760\Period1_2005\F4.doc 2/6
Då dessa fyra rader har utförsts ser stacken ut så här: Stackpekare efter R3 Stackpekare innan R0 ledigt R3 R2 R1 R0 Motsatsen till är POP POP Pop Register from Stack Description: This instruction loads register Rd with a byte from the STACK. The Stack Pointer is preincremented by 1 before the POP. This instruction is not available in all devices. Refer to the device specific instruction set summary. Operation: Rd STACK Syntax: Operands: Program Counter: Stack: POP Rd 0 d 31 PC PC + 1 SP SP + 1 16-bit Opcode: 1001 000d dddd 1111 Fortsätt programmet ovan med ett par rader till: R0 R1 R2 R3 POP R3 POP R2 Stackpekare efter POP R2 Stackpekare innan R0 ledigt R3 betraktas som ledigt R2 betraktas som ledigt R1 R0 Efter instruktionen POP R2 så pekar stackpekaren på den plats som vi har lagrat R2. Även R3 finns kvar. Dessa platser kommer att skrivas över vid nästa användning av stacken. Observera att och POP endast fungerar gentemot register. Lita aldrig på att något nedanför stackpekaren finns kvar oförändrat. 2005-09-08 H:\CT3760\Period1_2005\F4.doc 3/6
I C-kursen hade vi en uppgift som gick ut på att byta värden. int main(int argc, char *argv[]) { int a= 1; int b=5; int slask; printf("a = %d och b = %d \n",a,b); slask =a; a = b; b=slask; printf("\na = %d och b = %d \n",a,b); Efter programkörningen så innehåller a värdet 5 och b innehåller värdet 1. Hur gör man i asssembler? De del av programmet som gör motsvarande är R1 R5 POP R1 POP R5 Klart. Flytta ett I/O-register till stacken. Det som är aktuellt är att flytta SREG till stacken. Det kan vara bra att ha en kopia av detta register då stacken används av en subrutin. Man kan inte kopiera registret till stacken direkt, utan det måste göras via ett register. - - IN R4, SREG R4 - För att ta tillbaka från stacken. POP OUT - R4 SREG, R4 IN och OUT fungerar på I/O och register. 2005-09-08 H:\CT3760\Period1_2005\F4.doc 4/6
Subrutiner Mnemonic CALL ICALL RCALL long call Indirekt, adress i Z-reg relativt Ett litet exempel där jag tillfogat vad programräknaren har för värde..include "m16def.inc".def temp = R16.cseg.org 0 PC 0 rjmp RESET 1 RESET: ldi temp,low(ramend) 2 out SPL, temp 3 ldi temp,high(ramend) 4 out SPH, temp 5 clr r16 5 clr r17 7 clr r18 8 ldi r16,0x12 9 ldi r17,0x13 A rcall rutin B nop C nop D stop: rjmp stop E rutin: mov r18,r16 F add r18,r17 10 ret När instruktionen out SPH, temp utförts är stackpekaren definierad. Den har den hexadecimala adresssen 0x045F. När instruktionen rcall rutin har hämtats, så pekar programräknaren PC på nästa instruktion. Den finns på adress 0x00000B Instruktionen innebär ett hopp. Programräknarens innehåll sparas på stacken. Nästa lediga plats 0x45F 0x45E 0x45D 0x0B 0x00 2005-09-08 H:\CT3760\Period1_2005\F4.doc 5/6
Nytt värde på programräknaren blir 0x00000E vilket är 0x0b +4. Nu startar programmet från den nya adressen. När instruktionen RET hämtats så ställs programräknaren om. Den nya addressen hämtas från stacken. Programmet körs sedan fram till stop. Stackpekarens värde ökar med två efter det att återhoppsadressen hämtats. Nästa gång något skrivs till stacken kommer 0x0B att skrivas över. Genomgång av programmet tabell. Programet lagrar 16 byte i SRAM. Observera kodsegment.cseg och datasegment.dseg.cseg finns i flash-minnet och.seg finns i SRAM. Genomgång av programmet tabell2. Här finns koden och ett antal konstanter i flash-minnet. Ny instruktion LPM Ladda programminne. Z-registret används som pekarregister. LDI zh,high(2*konst) LDI zl,low(2*konst) Labeln konst har värdet 0000x30. Denna multipliceras med två. Värdet blir 0x60. Instruktionen LPM använder z-registret som pekare, men bara de 15 mest signifikanta bitarna. Pekaren får då åter ett effektivt värde som är 0x30. Sista biten är en nolla. LPM använder denna som instruktion att läsa in den minst signinfikanta byte. Man adderar sedan 1 till z registret. LPM kommer att uppfatta samma adress, men nu läses den mest signifikanta byten in. Genomgång över hur en 7-segmentdisplay är kopplad. Se bilaga. 2005-09-08 H:\CT3760\Period1_2005\F4.doc 6/6