Institutionen data- och informationsteknik 2011-10-31 Att placera program i FLASHrespektive RAM-minne Följande resonemang gäller för processorn MC9S12DG256B. Vi kommer i görligaste mån att skriva våra program i C men med några få inslag av in line-assembler. Vi skall studera hur vi kan använda XCC-miljön för att bygga upp applikationer där vi använder de minnen som finns inbyggda i processorn för att lagra vår applikation. Dessa minnen är då RAM- och ROM-minne (FLASH-minne). Vi skall se hur vi via initieringsfiler och länkarskript kan få våra applikationer att använda önskade delar av processorns minne. Vi kommer för våra applikationer att ha en tom processor utan fördefinierat programinnehåll, dvs vi lämnar MC12-miljön där vi hade en inbyggd debugger, ett rudimentärt operativsystem. Då vi vill åstadkomma fristående, självförsörjande program så uppkommer behovet att placera dessa program i minne som inte töms då strömmen stängs av, dvs i ROM vilket i vårt fall är FLASH-minne. Programmen skall dessutom gärna starta upp av sig själva då vi slår på strömmen eller trycker på Reset, dvs det behövs en resetvektor. Låt oss starta med att på samma sätt som i MC12 placera program i RAM-minne varefter vi, steg för steg, allt eftersom inför nödvändiga tillägg för att först skapa en FLASH-applikation och sedan en RAM-applikation med relokerade avbrottsvektorer. Nödvändig hantering varierar lite med hur vårt program är uppbyggt och vi kan dela upp det hela i nio steg 1. program i RAM-minne som bara innehåller lokala variabler 2. program i RAM-minne som innehåller oinitierade globala variabler 3. program i RAM-minne som innehåller initierade globala variabler 4. program i FLASH-minne som bara innehåller lokala variabler 5. program i FLASH-minne som innehåller oinitierade globala variabler 6. program i FLASH-minne som innehåller initierade globala variabler 7. program i FLASH-minne som innehåller initierade globala variabler med kopiering till RAM 8. program i FLASH-minne med avbrott (interrupt) 9. program i RAM-minne med relokerade avbrottsvektorer för avbrott Då uppstarten vid strömpåslag eller Reset är en form av avbrott, reset-adressen ligger ju in en avbrottsvektor, så kan vi i de sju första fallen inte få helt fristående applikationer utan de måste köras via en POD (BDM-laddare) så att vi kan sätta PC-räknaren till programmets startadress och starta programmet från denna adress. Vi använder också vår POD för att ladda CHALMERS TEKNISKA HÖGSKOLA Institutionen för data- och informationsteknik Sida 1 Avdelningen för datorteknik Besöksadress: Rännvägen 6 412 96 Göteborg
ner program till aktuellt minne. I fall 1, 2, 3 och 9 så har vi programmet i flyktigt minne, dvs programmet finns inte i datorn vid uppstart så här måste vi använda en POD varje gång vi slår på strömmen både för att ladda ner program och sedan behövs den också för att starta programmen, förutom i fall nio där start kan ske via den relokerade resetvektorn. Den nionde varianten kräver att vi förutom vår applikation i RAM-minne också placerar en applikation i FLASH-minne som relokerar avbrottsvektorerna till RAM-minne. Detta blir då två separata applikationer där relokeringsprogrammet i FLASH-minne kan ligga kvar permanent och användas av alla senare program placerade i RAM och som innehåller avbrott. Vi skapar en förenklad version av den relokering som sker i MC12:s debugger. Skillnaden är att i MC12 får vi utskrift till terminal om vi använder en oinitierad avbrottsvektor. Förutsättningen för detta är att det finns en möjlighet att skriva till terminal via SCI-interface. Det måste alltså finnas en initierad serieport och en utskriftsrutin vilket saknas i vårt system. För att illustrera hanteringen så använder vi oss av ett mycket enkelt program som bara gör PortB till en utgång och togglar ett ben i porten för att generera en fyrkantvåg. I de första sju fallen använder vi en fördröjning för att skapa periodtiden för hög respektive låg signal ut från PortB medan vi i de två sista fallen övergår till att generera periodtiden via avbrott från realtidsklockan. Fördröjning och avbrottstid är inte anpassade för att ge lika lång periodtid i de olika fallen. För att våra program skall hanteras på rätt sätt så måste vi ge direktiv till länkaren så att den länkar ihop de olika programdelarna till ett enda program och placerar olika typer av data på rätt ställen i minnet. Vi kan göra detta genom att göra menyvalet Project/Settings i XCCmiljön och ange direktiven under Linker/Options. Detta skulle dock betyda att vi för varje projekt skulle behöva mata in aktuella inställningar. Vi kan i stället välja att använda en skriptfil för att ge konfigureringen av länkaren och då kan vi ju återanvända denna skriptfil för olika projekt och vi gör några varianter på denna fil som passar för de olika applikationstyperna. Låt oss innan vi gå vidare titta på hur en sådan skriptfil kan vara uppbyggd. Skriptfil till länkaren Låt oss se på skriptfilen default.lsc (lsc = linker script) som är default länkarskript i utbildningsvarianten av XCC, filen finns i Bilaga 1. Denna fil är avsedd för labdatorn MC12 med sin inbyggda debugger där vi i normalfallet laddar program i RAM-minne. MC12 använder sig av processorn MC9S12DG256B som har tillgängligt RAM-minne i intervallet 0x1000 0x3FFF och det är då detta minnesutrymme som skriptfilen allokerar. Låt oss först rensa bort alla kommentarer så att vi ser filens verkliga innehåll. Vi gör samtidigt ett par editeringar för att öka läsbarheten. Vi får Kod 1 -M entry( start ) group( c, const_group ) abs group( r, test_group) init, sida 2
text, rodata, data, bss group( r, interrupt_vectors ) vectors layout 0x1000,0x3C80 <= test_group, 0x3F80,0x3FFF <= interrupt_vectors Kod 1 Vi ser att filen innehåller fyra olika strukturer, ett M-direktiv, en entry-deklaration, några gruppindelningar och en layout-struktur. Innehållet beskrivs ingående i XCC:s hjälpsystem men vi ger en kort beskrivning här. -M-direktivet gör att det vid länkningen kommer att skapas en map-fil (map = memory map) med samma namn som den genererade laddfilen men med ändelsen.map. map-fil innehåller information var programavsnitt, konstanter och variabler har placerats i de tillgängliga minnena. Laddfilen har samma namn som projektet men detta namn är efterföljt av _Debug eller _Final beroende på vilken typ av fil vi väljer att generera. Skillnaden mellan dessa filer är att debugvarianten innehåller information som en debugger kan använda sig av. Denna information gör det möjligt att stega sig fram i koden och sätta brytpunkter och samtidigt ger den oss möjlighet att i debugarbetet använda våra symboliska namn, våra lablar men samtidigt blir programfilen större än vad finalvarianten blir. Det naturliga är att generera en debugvariant av laddfilen under programutvecklingen för att till slut skapa en finalvariant för nedladdning när det hela är klart. Vi kan enkelt välja att generera en map-fil med annat namn om vi så önskar genom att ge ett filnamn efter -M-direktivet. Vi kommer att se exempel på map-filer längre fram i texten. Via group-direktivet så lägger vi in olika datasegment i grupper som vi sedan via layoutdirektivet placerar i olika minnesareor. Varje grupp kan innehålla ett eller flera segment och då vi anger flera segment i samma grupp så kommer dessa segment att placeras direkt efter varandra, i den ordning de är angivna, i den aktuella minnesarean. Vi har sex olika typer av segment abs init text rodata i detta segment placeras alla konstanter. Detta segment skall inte ges någon minnesadress då konstanterna kommer att assembleras in i själva programkoden och inte lagras separat i processorns minne detta segment skall placeras först och här kommer vår startup-fil med sina initieringar att placeras standardsegment för programkoden standardsegment för konstant initierad data (read only) sida 3
data bss standardsegment för initierad data standardsegment för oinitierad data Med hjälp av layout-direktivet anger vi då sedan start- och slutadress för de minnesareor där vi vill placera de olika grupperna. Vi ser i skriptfilen default.lsc att alla standardsegment utom abs är samlade i gruppen test_group och att gruppen sedan är placerad i minnesområdet 0x1000-0x3C80, vilket är den större delen av adressområdet för MC12:s RAM-minne. Att segmentet abs ligger för sig själv i en egen grupp beror på att det inte skall placeras i någon minnesarea och det finns då inte heller med i layout delen av filen. Dessutom har vi gjort en grupp kallad interrupt_vectors innehållande ett segment som kallas vectors. Detta är inget standardsegment utan segmentet har deklarerats i vår programkod via ett så kallat #pragma-direktiv. #pragma-direktiv används för att skapa nya namn till våra minnessegment som sedan kan placeras i önskade minnesareor via ovanstående skriptfil. Vi har syntaxen #pragma TEXT DATA BSS <segment_namn> där vi då ger ett segment av typen text, data eller bss ett nytt namn. Observera att <segment_namn> inte får vara längre än 16 tecken. Mer om detta senare. I den defaulta filen används detta för att placera de till RAM relokerade avbrottsvektorerna längst upp i RAMminnet. I debuggerkoden till MC12 har vi alltså placerat de ommappade avbrottsvektorerna i ett segment kallat vectors som är deklarerat via ett #pragma-direktiv. Detta segment är sedan placerat längst upp i RAM-minnet så att de ommappade avbrottsvektorerna får samma adresser som de verkliga avbrottsvektorerna bortsett från att det första hexadecimala F:et i deras verkliga adresser har ersatts med en 3:a, till exempel är IRQ-avbrottets avbrottsvektor ommappad från 0xFFF2 till 0x3FF2. Vi återkommer till detta lite längre fram då vi beskriver hur vi gör för att mappa om avbrottsvektorerna till RAM. Vad gäller övriga adresser i RAM-minnet så är adresserna under 0x1000 reserverade för processorns minnesmappade registeruppsättning samt som I/O-area för labmoduler medan intervallet 0x3C81 0x3F7F används av debuggern. Låt oss också titta på systemets startup-fil. Systemets startup-fil Till systemet finns en default startup-fil _startup.s12 som skall placeras i startup-segmentet och därmed hamna först i programmet. Denna fil ligger med i installationen av XCC och sätter bland annat upp systemets stackpekare och initierar systemets C-bibliotek. I normalfallet kommer filen automatiskt att inkluderas i våra program. I övrigt förlitar sig startup-filen på initieringar som finns i debuggern, bland annat initiering av processorns klockkrets med PLL samt initiering av en serieport för kommunikation med systemet. I denna seriekommunikationsdel finns de enkla funktionerna outchar, tstchar och inchar som används för att enkelt läsa och skriva tecken via serieporten men dessa funktioner är inte kompletta utan anropar i sin tur outchar- och tstcharfunktioner som finns i debuggern. sida 4
Detta betyder att när vi skall köra systemet utan debugger så måste vi själva initiera klockkretsen och behöver vi en serieport så får vi själva initiera denna och vi får skriva våra egna outchar-, tstchar- och inchar-rutiner. Startup-filen innehåller dessutom en del funktioner som är nödvändiga om vi skall använda oss av dynamisk minnesallokering. Vi kommer inte att göra detta varför vi lämnar dessa delar därhän. Filen _startup.s12 finns i Bilaga 2 och vi kommer här att kortfattat beskriva dess funktion. Filen inleds med ett antal konstantdeklarationer som då är placerade i abs-segmentet. Här deklareras bland annat ett antal konstanter som används för att sätta upp stacken. Efter detta övergår vi till det första kodsegmentet där en applikation skall startas efter omstart. Segmentet kallas init och detta ligger som vi minns före text-segmentet i den grupp vi deklarerade i länkarens skriptfil. I startup-segmentet ligger labeln start till vilken systemets resetvektor refererar, dvs det här hit programmet går vid reset. Efter detta sätter vi initialvärdet på stackpekaren till TOS (Top Of Stack), en konstant som tidigare är deklarerad som 0x3C80 vilket är den högsta fria adressen i processorns RAMminne. Adresserna ovanför 0x3C80 används som vi såg dels av debuggern och är dels reserverade för de ommappade avbrottsvektorerna. När detta är klart så hoppar vi till en subrutin som initierar en serieport för simulatorn. En test av minnesinnehållet på adress 0x001B som innehåller komponentinformation för den aktuella enheten gör att vi kan avgöra om vi kör i hårdvara eller i simulator. Kör vi i hårdvara så initieras ingen serieport utan då används debuggerns initiering av serieporten och vi vill inte störa denna initiering. Rutinen initierar serieportens baudrateregister men då simulatorn inte kör i realtid så kan hastigheten sättas till vad som helst. Efter detta är initieringen klar och vi gör ett hopp till labeln _main som då skall ge hopp till huvudfunktionen main(void) i vår C-applikation eller till labeln _main om vi har en applikation med huvudprogrammet i assembler. Vi skall se senare att funktionsnamn i C vid kompileringen omvandlas till lablar i assemblerkod och att dessa lablar är lika med funktionsnamnet men i labeln kommer namnet att föregås av en understrykning (_). Utöver detta innehåller startup-filen de ovan nämnda assemblerrutinerna för att läsa in tecken från serieporten, testa om tecken finns på serieporten samt skriva tecken till densamma. Vi ser att dessa rutiner hoppar till adresser strax över 0xC000 vilket är den FLASH-minnesadress där vår debugger startar. Vi ser att rutinerna innehåller absoluta hopp till debuggern och inte subrutinanrop. Det beror på att rutinerna i debuggern redan innehåller RTS och dessa ger återhopp då vi lämnar rutinerna. Nödvändiga förändringar i systemets startup-fil Då vi nu saknar debugger så måste vi se till att kontrollera att processorn går upp med önskad klockfrekvens, i detta fall 8 MHz, samt att PLL:n låser, dvs går igång med rätt frekvens och vi får komplettera startup-filen med detta. Vi väljer att konfigurera systemet så att processorns systemlocka får samma frekvens som oscillatorn har, dvs 8 MHz. Vi gör detta via CRG-enheten (Clock and Reset Generator) genom att i CRG-registret CLKSEL välja att generera systemklockan från PLL-klockan och systemklockan kommer då att få halva PLL-klockans frekvens, dvs vi skall ha PLL-klocksfrekvensen 16 MHz. Alternativet är att generera systemklockan direkt från oscillatorns klocka och dess frekvensfrekvens men då skulle systemklockan få halva oscillatorklockfrekvensen, dvs 4 MHz. sida 5
PLL-klockans frekvens ges av värdena i registren SYNR (CRG Synthesizer Register) och REFDV (CRG Reference Divider Register) enligt PLLCLK SYNR 1 2 OSCCLK REFDV 1 Vi ser att PLL-klockan får önskad frekvens (dubbla den önskade systemklockfrekvensen) om vi sätter både SYNR och REFDV till noll. Innan vi låter PLL-klockan styra systemklockan så måste vi se till att PLL:ns VCO har låst på rätt frekvens och detta indikeras av en etta (1) i bit tre (3) i CRG:ns flaggregister (CRGFLG). Vi får Kod 2 devinit: #define SYNRVal 0 #define REFDVVal 0 ; Setup clock references LDAB #SYNRVal ; STAB SYNR LDAB #REFDVVal; STAB REFDV BRCLR CRGFLG,#LOCK,. ; vänta på låst PLL BSET CLKSEL,#PLLSEL ; generera systemklocka från PLL. RTS Kod 2 De symboliska namnen bygger på h-filen HS12_DG.h varför denna fil måste inkluderas i programmet. Denna fil kan alltså användas både i assembler och C. Skall vi använda en serieport så måste även denna initieras. Det gör vi enklast genom att modifiera startup-filens serieportsinitiering för simulatorn genom att ta bort testen för hårdvara samt initiera serieporten för baudraten 9,6 kbaud och dessutom aktivera porten för både sändning och mottagning men inte aktivera några avbrott från serieporten. Denna initiering lämnas till laboranterna själva. Vi tar också bort anropet av serieportsrutiner i debuggern och lämnar dem tills vidare som tomma rutiner innehållande bara ett RTS. Behöver vi serieporten så får vi fylla rutinerna med innehåll senare. Vi har den modifierade startupfilen _startup_mod.s12 i Bilaga 3. I kommande avsnitt kommer vi att införa några förändringar i startup-filen för att anpassa den till våra applikationer. Vi inför dessa förändringar då de blir aktuella. Dessa applikationer kommer inte att använda någon serieport. Låt oss nu se på våra programvarianter som skall placeras i vår mikrokontroller utan debugger. Program i RAM-minne som bara innehåller lokala variabler Vi skriver ett enkelt program för den applikation vi beskrev tidigare, dvs vi togglar Port B med jämna mellanrum, Kod 3 sida 6
#include "..\HS12_DG.h" #include "..\reg_macro.h" void main(void) char temp; short i,j; REG8(DDRB)=0xFF; temp=0; while(1) REG8(PORTB)=temp; temp=!temp; for (i=0;i<10;i++) for (j=0;j<10000;j++); Kod 3 Koden till filen local_noirq_ram.c återges också i Bilaga 4. I den inkluderade h-filen HS12_DG.h deklareras adresserna till HC12:s olika register och även de individuella bitarna i dessa register med samma namn som de har i HC12-manualerna. I h-filen reg_macro.h deklareras funktioner för att via pekare adressera dessa register från program skrivna i C. Dessa h-filer ligger ett steg högre upp i katalogstrukturen (..\) för att lätt kunna användas av flera applikationer i parallella mappar. Vi ser att programmet bara innehåller lokala variabler, en lokal variabel (temp) för att hantera det värde vi skickar till Port B och två lokala variabler (i och j) för att skapa en nästlad fördröjningsfunktion. För lokala variabler kommer det inte att allokeras något minne utan för dessa variabler bereds i stället plats på stacken. Vi kan se detta om vi vid kompileringen använder konfigureringsswitchen S i Project/Settings (se nedan) som gör att vi behåller den assemblerfil som genereras som mellanformat då C-filen kompileras. Så snart vi har variabler i vårt program, dvs data som vi skall kunna både läsa och skriva så måste dessa vara placerade i minnesareor där vi kan både läsa och skriva. Vi måste alltså placera dem i RAM-minne. Eftersom vi här bara använder stacken för våra variabler så är det stacken som måste placeras i RAM. Vi måste också se till att ha en stack uppsatt då vi börjar använda subrutiner eftersom vid anrop av dessa så lagras återhoppsadressen på stacken. Applikationen använder sig faktiskt av subrutiner, trots att det inte finns några i vårt program, eftersom vi anropar sådana i startup-filen, dvs även av detta skäl behöver via ha en stack uppsatt redan från första början då startup-filen är det första som länkas in i applikationen. Då stacken växer nedåt, mot lägre adress, så bör vi då placera stackens startadress TOS (Top Of Stack) högst upp i RAM-minnet för att utnyttja detta minne som bäst, dvs på RAM-minnets högsta adress. Som vi minns är den högsta RAM-adressen 0x3FFF för processorn MC9S12DG256B och vi kan placera TOS på denna adress då vi än så länge saknar relokerade avbrottsvektorer. Vi gör denna lilla ändring i vår startup-fil. I vårt fortsatta labsystem MC12S som är en ren processor utan debugger finns inte någon serieport ansluten vilket betyder att vi dessutom skall utelämna initieringen av serieporten i startup-filen och göra initieringen av serieporten om vi skulle behöva denna. Vi kan naturligtvis också utelämna rutinerna som använder serieporten dvs outchar, tstchar och sida 7
inchar. Vi får en startup-fil enligt Bilaga 5. Vi kallar filen _startup_noirq_ram.s12. Lägg märke till att användande av denna fil i stället för defaultfilen kräver en förändring i XCC:s Project/Settings-fönster. Vi återkommer strax till detta. Låt oss nu övergå till länkarskriptet. Då vår kod varken har konstanter eller initierade eller oinitierade variabler kan vi tycka att det skulle räcka med att skapa ett text-segment för själva programkoden medan de övriga segmenten utelämnas. XCC-miljön förutsätter dock att de andra segmenten finns med, även om de är tomma, så vi får ändå ta med dem. Då dessa segment inte används så spelar det ingen roll var vi placerar dem men låt oss placera dem på ett sätt som är lämpligt för följande applikationer med globala variabler. Då vi inte har några relokerade avbrottsvektorer så kan denna grupp utelämnas. Vi får Kod 4 Kod 4 -M group( c, const_group ) abs group( r, ram_group) init, text, rodata, data, bss layout 0x1000,0x3FFF <= ram_group Filen, noirq_ram_script.lsc, finns i Bilaga 6 med kommentarer. Låt oss nu se på hur vi konfigurerar XCC för detta genom Project/Settings-fönstret. Vi får Figur 1. sida 8
Figur 1: Project/Settings för program i RAM-minne Vi har kompletterat med switchen S under Options i C Compiler-rutan. Detta gör att vi kommer att behålla assemblerfilen som skapas vid C-kompileringen så att vi kan titta i denna fil om vi vill. Utan denna switch kommer filen automatiskt att raderas. Filen får samma namn som C-filen men med ändelsen.s och hamnar i underbiblioteket debug eller final beroende på vilken typ av applikation vi gör. Switchen L under Options i Assembler-rutan gör att det kommer att genereras en listfil då assemblerfiler assembleras. Vi får dock tyvärr inga listfiler från assemblerfiler som C- kompilatorn har översatt till assembler. I Target-rutan klickar vi bort Use standard startup så att vi använder den lokala _startup_noirq_ram.s12-filen i stället för den defaulta _startup.s12. Då denna fil inte automatiskt kommer att inkluderas i vårt projekt så får vi inte glömma att lägga till filen manuellt. Till sist har vi lagt in vår nya skriptfil noirq_ram_script.lsc i Linker-rutan. Här är filen placerad ett steg upp i biblioteksstrukturen (..\) för att enkelt kunna användas även av andra applikationer i arbetsarean (workspace). Den resulterande konfigurationen visas i mapfilen local_noirq_ram_debug.map som finns i Bilaga 7. Filen visar inom vilket minnesområde vår grupp av segment, ram_group, har hamnat samt var de enskilda segmenten finns. Vi ser också på vilka adresser de länkardefinierade konstanterna som inleder och avslutar de olika segmenten, till exempel text start respektive text end, finns. Här ser vi också storleken på respektive segment. Vi kan se att det finns innehåll i både bss- och rodata-segment trots att vår egen applikation saknar detta. Innehållet kommer från initieringen av C-biblioteket. Slutligen kan vi se på vilka adresser våra globala symboler inklusive programlablar, till exempel _main, ligger. Det framgår också från vilka filer lablarna kommer. Filnamnen anges sida 9
som de assemblerade objektsfilerna med ändelse.o12 oberoende om de härstammar från C- eller assemblerfiler. Låt oss nu lägga in oinitierade globala variabler. Program i RAM-minne som innehåller oinitierade globala variabler Vi gör en liten men viktig förändring i programkoden för vår applikation Kod 5 #include "..\HS12_DG.h" #include "..\reg_macro.h" char temp; void main(void) short i,j; REG8(DDRB)=0xFF; temp=0; while(1) REG8(PORTB)=temp; temp=!temp; for (i=0;i<10;i++) for (j=0;j<10000;j++); Kod 5 Koden, global_noirq_ram.c, återges också i Bilaga 8. Vi ser att det enda vi har gjort är att flytta ut deklarationen av variabeln temp ur funktionen main(), dvs vi har definierat om variabeln som global så att den kan nås av alla delar av vårt program. Det betyder att variabeln temp inte längre kan hanteras via stacken utan den måste få en egen plats i minnet och då vi skall kunna skriva till variabeln så måste den vara placerad i RAM-minne. Då det är en oinitierad variabel så kommer den att placeras i bss-segmentet men då hela vår applikation ligger i RAM-minne så är detta ingen komplikation utan bsssegmentet ligger redan i rätt minnestyp. Vi behöver alltså inte ändra i länkarskript eller statupfil då vi inför globala variabler i en RAM-minnesapplikation. Den resulterande mapfilen global_noirq_ram_debug.map återges i Bilaga 9. Om vi jämför denna fil med den tidigare mapfilen då vi bara hade lokala variabler så ser vi att text-segmentet har blivit något mindre beroende på att vi har variabeln på en minnesposition och slipper pusha och poppa från stack. Samtidigt har bss-segmentet blivit större eftersom vår oinitierade variabel hamnar där och vi kan se på vilken adress variabeln _temp ligger. Låt oss se vad som händer om vi i stället har en initierad variabel. sida 10
Program i RAM-minne som innehåller initierade globala variabler Vi gör återigen en liten men viktig förändring i programkoden för vår applikation Kod 6 #include "..\HS12_DG.h" #include "..\reg_macro.h" char temp=0; void main(void) short i,j; REG8(DDRB)=0xFF; while(1) REG8(PORTB)=temp; temp=!temp; for (i=0;i<10;i++) for (j=0;j<10000;j++); Kod 6 Koden, global_init_noirq_ram.c, återges också i Bilaga 10. Vi ser att vi har flyttat ut nollställningen av variabeln temp och gjort denna i form av en initiering av variabeln då den deklareras. Variablen skall nu placeras i segmentet för initierad data, dvs data-segmentet. Även data-segmentet finns med i vårt startskript så vi behöver inte göra några ändringar. Den resulterande mapfilen global_init_noirq_ram_debug.map återges i Bilaga 11. Om vi jämför denna fil med föregående mapfil så ser vi att text-segmentet återigen har blivit något mindre beroende på att vi nu inte nollställer temp i programkod utan via initiering. Samtidigt har bss-segmenet återfått samma storlek som då vi hade bara lokala variabler, dvs variabeln temp har försvunnit därifrån men i stället har vi fått innehåll i data-segmentet och vi kan se på vilken adress i segmentet som variabeln temp ligger. Program i FLASH-minne som bara innehåller lokala variabler Låt oss återgå till vår enkla applikation med bara lokala variabler, Kod 3, men nu placera applikationen i FLASH-minne. Vi måste göra ett litet tillägg i vår programkod och får Kod 7 sida 11
#include "..\HS12_DG.h" #include "..\reg_macro.h" void main(void) char temp; short i,j; REG8(DDRB)=0xFF; temp=0; while(1) REG8(PORTB)=temp; temp=!temp; for (i=0;i<10;i++) for (j=0;j<10000;j++); #pragma DATA UNSEC_address unsigned short unlock = 0xFFFE; Kod 7 Koden till filen local_noirq_flash.c återges också i Bilaga 12. Som synes har vi kompletterat C-filen med ett #pragma-direktiv. Detta direktiv används tillsammans med direktiv i länkarskriptet (se nedan) för att skriva till specifika adresser i FLASH-minne. unlock är bara ett godtyckligt namn som vi aldrig kommer att använda oss av men konstanten måste ha ett namn. Vi använder direktivet för att skriva 0xFE till adress 0xFF0F i FLASH-minne. Egentligen skriver vi en word (två byte), 0xFFFE, till adress 0xFF0E. Detta bara för att länkaren inte gillar att placera data på udda adresser så vi får använda en wordbaserad adress. Vi gör skrivningen för att ta bort den säkerhetsbyte som ligger på adress 0xFF0F som ett skydd mot läsning från FLASH-minnet. Detta skydd är till för att en användare av ett färdigprogrammerat chip inte skall kunna läsa av (stjäla) den inprogrammerade programkoden. Vi ser att programmet som tidigare bara innehåller lokala variabler. För lokala variabler kommer det, som vi minns, inte att allokeras minne utan för dessa görs det plats på stacken och då stacken måste vara skrivbar så måste denna placeras i RAM-minne. Vi kan som tidigare placera stackens startadress TOS (Top Of Stack) högst upp i RAM-minnet för att utnyttja detta minne som bäst, dvs på RAM-minnets högsta adress 0x3FFF. Vi kan behålla vår tidigare startupfil _startup_noirq_ram.s12, Bilaga 5. Låt oss nu övergå till länkarskriptet. Då vår kod varken har konstanter eller initierade eller oinitierade variabler så räcker det egentligen men att skapa ett text-segment för själva programkoden medan de övriga segmenten kan utelämnas men som vi nämnt tidigare så förutsätter XCC att de andra segmenten finns med, även om de är tomma, så vi får ändå lägga med dem. Då initieringen av C-biblioteket använder även dessa segment så placerar vi dom på ett sätt som är lämpligt för följande applikationer med globala variabler. sida 12
Observera att vi också har lagt in en grupp (unsec_group) för det värde (0xFFFE) vi skriver till adress 0xFF0E för att göra FLASH-minnet läsbart. Då vi inte har några reallokerade avbrottsvektorer så kan denna grupp utelämnas, däremot finns ju alltid utrymmet för de verkliga avbrottsvektorerna längst upp i FLASH-minnet (från adress 0xFF80) så vi får inte använda den delen av FLASH-minnet i allokeringen. För enkelhetens skull så låter vi flash_group sluta direkt nedanför unsec_group. Vi får Kod 8 Kod 8 -M group( c, const_group ) abs group( r, ram_group) bss group( r, FLASH-_group) startupseg, text, rodata, data, group( r, unsec_group) UNSEC_address layout 0x1000,0x3FFF <= ram_group, 0xC000,0xFF0D <= flash_group, 0xFF0E,0xFF0F <= unsec_group Filen, noirq_flash_script.lsc, finns också i Bilaga 13 med kommentarer. Vi konfigurerar XCC via Project/Settings-fönstret på samma sätt som tidigare, men får naturligtvis byta till rätt länkarskript. Lägg märke till att vi åter skall ha en egen startup-fil och inte stardardfilen. Vi får Figur 2. sida 13
Figur 2: Project/Settings för program i FLASH-minne Den resulterande mapfilen local_noirq_flash_debug.map återges i Bilaga 14. Här ser vi hur ett antal av våra segment nu har hamnat i FLASH-minne. Segmentet för oinitierade variabler, bss-segmentet finns dock kvar i RAM-minnet och har samma storlek som tidigare. Dessa variabler måste ju vara skrivbara. Lägg märke till att detta segment ligger först i den användbara delen av RAM-minnet (från adress 0x1000) då vi inte har några andra programdelar i detta minne. Vi har också har fått en ny grupp, unsec_group, i FLASH-minnet för skrivningen som öppnar FLASH-minnet för läsning. Vi ser också att konstanten _unlock som vi skriver för att öppna FLASH-minnet för läsning ligger bland våra globala symboler. Låt oss nu lägga in oinitierade globala variabler. Program i FLASH-minne som innehåller oinitierade globala variabler Vi gör samma förändring i programkoden som tidigare, dvs vi flyttar ut variabeln temp ur main-funktionen och gör variabeln global och får Kod 9 #include "..\HS12_DG.h" #include "..\reg_macro.h" char temp; void main(void) short i,j; sida 14
REG8(DDRB)=0xFF; temp=0; while(1) REG8(PORTB)=temp; temp=!temp; for (i=0;i<10;i++) for (j=0;j<10000;j++); #pragma DATA UNSEC_address unsigned short dummy = 0xFFFE; Kod 9 Koden, global_noirq_flash.c, återges också i Bilaga 15. Variabeln temp har nu blivit global och kan inte längre hanteras via stacken utan den måste få en egen plats i minnet och då vi skall kunna skriva till variabeln så måste den vara placerad i RAM-minne. Då det är en oinitierad variabel så kommer den att placeras i bss-segmentet och det var därför som vi redan i filen noirq_flash_script.lsc (Kod 8) placerade detta segment i RAM-minne. Lägg märke till att vi har kvar vår skrivning till adress 0xFF0E för att göra FLASH-minnet läsbart. Eftersom vi införde detta redan i konfigureringen för programmet i FLASH-minne med bara lokala variabler så kan vi använda samma konfigurering även här och inställningarna i Figur 2 gäller fortfarande. Den resulterande mapfilen global_noirq_flash_debug.map återges i Bilaga 16. Vi kan se att bss-segmentet i RAM-minnet har vuxit för att rymma variabeln temp. Vi kan naturligtvis också se adressen till den oinitierade variabeln temp i bss-segmentet. Vi kommer inte att gå in på konstanter men låt oss ändå nämna vad som kommer att hända om vi har sådana i vårt FLASH-program. Konstanter skall aldrig ändras utan bara läsas och kommer dessutom att kompileras in i själva koden och inte ta plats i minnet så de är inga problem. Även konstant initierade variabler, deklarerade via const-direktivet, tillhör själva programmet och då de aldrig skall ändras så kommer länkaren inte att göra plats för dessa i RAMminne utan de placeras i FLASH-minnet och kommer att hamna i rodata-segmentet. Denna typ av data kan till exempel vara uppslagstabeller av olika slag. Låt oss åter göra vår variabel initierad. Program i FLASH-minne som innehåller initierade globala variabler Vi gör samma förändring i programkoden som tidigare, dvs vi flyttar ut nollställningen av variabeln temp ur main-funktionen och gör den i form av en initiering och får Kod 10 #include "..\HS12_DG.h" #include "..\reg_macro.h" char temp=0; sida 15
void main(void) short i,j; REG8(DDRB)=0xFF; while(1) REG8(PORTB)=temp; temp=!temp; for (i=0;i<10;i++) for (j=0;j<10000;j++); #pragma DATA UNSEC_address unsigned short dummy = 0xFFFE; Kod 10 Koden, global_init_noirq_flash.c, återges också i Bilaga 17. Variabeln temp har nu flyttats över från bss-segmentet i RAM-minne till data-segmentet i FLASH-minne. Vi ser detta i map-filen global_init_noirq_flash_debug.map i Bilaga 18. Om vi tänker efter så inser vi att detta inte är så bra då vi i vårt program måste kunna skriva till variabeln och vi kan inte skriva till FLASH-minne. Vi kan då undra om variabeln inte kunde ha legat i RAM-minne i stället men variabeln skall ju inte bara ha en plats i minnet utan den skall ju vara initierad till ett startvärde och detta värde är en del av vår programkod som ligger i FLASH-minne. Resultatet är att variabeln måste kopieras över från FLASH-minne till skrivbart RAM-minne efter att vi har placerat programmet i FLASH-minne. Vi får då skriva programkod för detta och vi kan inse att det för program i FLASH-minne är enklast att undvika detta genom att använda oinitierade variabler och initiera dessa via programkod i stället. I XCC finns det dock ett sätt att automatisera detta. Program i FLASH-minne som innehåller initierade globala variabler med kopiering till RAM Vi behåller samma programkod som i föregående fall (Kod 10, Bilaga 17) men gör en förändring i projektkonfigureringen. Vi lägger till flaggan P för länkaren, Figur 3. sida 16
Figur 3: Project/Settings för program i FLASH-minne med kopiering av initierade variabler til RAMminne Denna flagga gör att systemet automatiskt kommer att köra en programslinga som ligger i C- biblioteket och som kopierar initierade variabler från FLASH- till RAM-minne i vår applikation samt allokerar plats för de kopierade variablerna i RAM-minne. I programmet kommer adresser också att ändras så att vårt program kommer att referera till de kopierade variablerna i det relokerade data-segmentet i RAM-minne och inte till de verkliga variablerna i datasegmentet i FLASH-minne. Vi får map-filen global_init_p_noirq_flash_debug.map i Bilaga 19 där vi kan se att vi har fått ett nytt segment i RAM-minne allokerat för att rymma det kopierade data-segmentet. Program i FLASH-minne som innehåller avbrott Låt oss nu försöka göra om programmet så att det använder avbrott. Vi tar bort fördröjningen och använder i stället avbrott från realtidsklockan (RTI) för att styra periodtiden hos fyrkantvågen. Vi måste nu införa en avbrottsrutin och denna ska vara deklarerad med det inledande direktivet interrupt (inleds av dubbla understrykningar, ) vilket ger en något annorlunda assemblerkod jämfört med en vanlig subrutin. Skillnaden består i att vid en vanlig subrutin så lagras bara återhoppsadressen på stacken och vid återhopp via RTS så återläses denna adress. Vid avbrott däremot placeras alla processorns register på stacken och vid återhopp från avbrottsrutinen via RTI så skall alla dessa register återläsas i omvänd ordning. Ordet interrupt anger då för kompilatorn att rutinen skall avslutas med RTI och inte RTS då den översätts till assembler. Detta räcker nu inte för att vår applikation skall fungera. Hur skall processorn veta att den skall hoppa till den avbrottsrutin som vi kallar Irq_RTI då RTI-avbrottet kommer? sida 17
Vår processor HCS12 har vid avbrott samma typ av funktion som vi har sett tidigare för andra processorer. Vid avbrottet så går processorn till den adress i avbrottsvektortabellen som hör till det aktuella avbrottet och hämtar där adressen till den avbrottsrutin som skall utföras varefter hopp sker till denna adress. I HCS12 ligger avbrottsvektorerna längt upp i minnet, från adress 0xFFFF och en bit nedåt. Vektorernas lägsta adress är 0xFF8C men utrymmet ner till 0xFF80 är reserverat varför vi kan se detta som den aktuella minnesarean. Denna del av minnet består av FLASH-minne som visserligen går att programmera men antalet programmeringscykler är begränsat (cirka 10000) och det skulle dessutom vara praktiskt att även kunna utnyttja avbrott i program som placeras i RAM-minne. För att göra detta så måste vi ta till en del knep som vi skall se på senare. Vi börjar med att placera avbrottsapplikationen i FLASH-minne. Eftersom vi inte kommer att göra några ändringar i RAM-minnet så kan vi behålla den tidigare startupfilen _startup_noirq_ram.s12, Bilaga 4. Då avbrottet genereras så kommer processorn att gå till den adress i avbrottsvektortabellen som tillhör det aktuella avbrottet (här RTI-avbrott som finns på adress 0xFFF0) och där läsa adressen till den rutin som den skall hoppa till. Vi måste alltså på något sätt se till att adressen till avbrottsrutinen Irq_RTI hamnar på denna adress i avbrottsvektortabellen. Även Resetvektorn ligger i denna tabell så vi kan på samma gång ordna så att processorn startar upp som den skall vid strömpåslag eller reset. Vid uppstart eller reset så skall den gå till labeln start (OBS två understrykningar) som ligger i filen _startup_noirq_ram.s12. Vi kan inte låta programmet initiera avbrottsvektorerna genom att till aktuell adress i avbrottstabellen skriva adressen till den rutin till vilken vi skall hoppa eftersom FLASH-minnet inte är skrivbart från vårt program. Vårt program skall nu innehålla en huvudrutin, en initieringsrutin för RTI-avbrottet, en avbrottsrutin och i vår första lösning två separata data-segment som placerar adresserna till våra två avbrottsrutiner på tillhörande avbrottsvektorers adresser. Vi använder en snarlik metod till den som vi tidigare använde för att göra göra FLASH-minnet läsbart för att skriva in adresserna till våra avbrottsrutiner i avbrottsvektorerna. Då de två avbrott vi använder oss av inte ligger på efterföljande adresser i avbrottsvektortabellen så får vi använda två nya #pragma-direktiv som innehåller pekare till våra avbrottsrutiner. Lägg också märke till att det tidigare #pragma-direktivet för att göra FLASH-minnet läsbart finns kvar. I huvudprogrammet anropar vi bara rutinen för initiering av RTI-avbrottet varefter vi lägger oss i en oändlig loop och väntar på avbrott. Kom ihåg att start ligger i startup-filen så funktionen måste deklareras som extern i vår C-fil. I koden skrivs den som _start som av kompilatorn kommer att tolkas som namnet på en C-rutin som i assembler översättas till labeln start. I avbrottsinitieringen RTI_Init sätter vi tiden mellan avbrott till via registret RTICTL. Vi kommer att få en fyrkantvåg med frekvensen 3,81 Hz, kom ihåg att det 16 16 2 krävs två på varandra följande avbrott för att generera en period hos fyrkantvågen, ett avbrott för positiv halvperiod och ett avbrott för negativ halvperiod. Genom att ett-ställa bit 7 i registret CRGINT så tillåter vi RTI-avbrott. Till sist använder vi inline-assembler för att ge assemblerdirektivet CLI så att vi överhuvudtaget tillåter något bortmaskningsbart avbrott. I avbrottsrutinen Irq_RTI togglar vi PortB. Vi skriver dessutom en etta (1) till bit 7 i registret CRGFLG för att bekräfta avbrottet och därmed undvika ett nytt avbrott då vi återvänder från avbrottsrutinen. sida 18 systemklockan
Programmet blir naturligtvis lite mer komplicerat. Vi får Kod11 #include "..\HS12_DG.h" #include "..\reg_macro.h" char temp; void RTI_Init(void); extern void _start(void); interrupt void Irq_RTI(void); void main(void) REG8(DDRB)=0xFF; temp=0; RTI_Init(); while(1); void RTI_Init(void) REG8(RTICTL)=RTR6 RTR5 RTR4 RTR3 RTR2 RTR1 RTR0; REG8(CRGINT)=RTIE; asm(" CLI"); //0x7F //0x80 interrupt void Irq_RTI(void) REG8(PORTB)=temp; temp=!temp; REG8(CRGFLG)=REG8(CRGFLG) RTIF; //0x80 #pragma DATA UNSEC_address unsigned short unlock = 0xFFFE; #pragma DATA RTI_address interrupt void (*irq_rti[])() = Irq_RTI ; #pragma DATA RESET_address sida 19
interrupt void (*irq_reset[])() = _start ; Kod 11 Vi har använt oss av konstanter deklarerade i filen HS12_DG.h för att öka kodens läsbarhet. Koden, single_irq_flash.c, återges också i Bilaga 20. Då vi använder segment i skriptfilen för att sätta upp de aktuella avbrottsvektorerna så måste vi ta med dessa segment i skriptfilen. Vi får Kod 12 -M group( c, const_group ) abs group( r, ram_group) bss group( r, FLASH-_group) startupseg, text, cdata, data, group( r, unsec_group) UNSEC_address group( r, RTI_IRQ_vector ) RTI_address group( r, RESET_IRQ_vector ) RESET_address layout sida 20
0x1000,0x3FFF <= ram_group, 0xC000,0xFEFF <= flash_group, 0xFF0E,0xFF0F <= unsec_group, 0xFFF0,0xFFF1 <= RTI_IRQ_vector, 0xFFFE,0xFFFF <= RESET_IRQ_vector Kod 12 Filen, single_irq_flash_script.lsc, finns också i Bilaga 21 med kommentarer. Länkningen ger mapfilen single_irq_flash_debug.map enligt Bilaga 22. Vi ser att vi jämfört med tidigare mapfiler har fått med segment för avbrottsvektorerna för RTI- och RESET-avbrott. Vi ser också att i dessa segment ligger pekarna till anropslablarna för våra avbrottsrutiner. Mer generell lösning för program i FLASH-minne som innehåller avbrott Låt oss göra en lösning som är lite mer generell och som vi enkelt kan anpassa då vi använder andra avbrott. Vi fyller hela avbrottsvektortabellen med anropslablar till avbrottsrutiner men vi skriver inte alla dessa rutiner utan skriver bara rutiner för de avbrott vi skall använda medan vi omdirigerar övriga, oanvända avbrott till en tom, generell avbrottsrutin, Generic_Handler som inte gör någonting. Vi får avbrottsvektortabellen Kod 13 #pragma DATA vectors interrupt void (*irqvecs[])() = // FF80-FF8B is reserved (short *) 0xFFFF, (short *) 0xFFFF, (short *) 0xFFFF, (short *) 0xFFFF, (short *) 0xFFFF, (short *) 0xFFFF, Irq_PWMEShutdown, Irq_PortPInt, Irq_MSCAN4Tx, Irq_MSCAN4Rx, Irq_MSCAN4Errs, Irq_MSCAN4WakeUp, // FF98-FF9F is reserved (short *) 0xFFFF, (short *) 0xFFFF, (short *) 0xFFFF, (short *) 0xFFFF, // Byteflight FFA0 Irq_BFGen, sida 21
Irq_BFSync, Irq_BFRec, Irq_BFRx, // CAN1 FFA8 Irq_MSCAN1Tx, Irq_MSCAN1Rx, Irq_MSCAN1Errs, Irq_MSCAN1WakeUp, // CAN0 FFB0 Irq_MSCAN0Tx, Irq_MSCAN0Rx, Irq_MSCAN0Errs, Irq_MSCAN0WakeUp, // NV memory... Irq_Flash, Irq_EEPROM, // FFBC Irq_SPI2, Irq_SPI1, Irq_IICBus, Irq_BDLC, Irq_SCMEVect, Irq_CRGLock, Irq_PACCBOv, Irq_ModDnCtr, Irq_PortHInt, Irq_PortJInt, Irq_ATD1, Irq_ATD0, Irq_SCI1, Irq_SCI0, Irq_SPI0, Irq_PACCAEdge, Irq_PACCAOv, Irq_TimerOv, Irq_TimerCh7, Irq_TimerCh6, Irq_TimerCh5, Irq_TimerCh4, Irq_TimerCh3, Irq_TimerCh2, Irq_TimerCh1, Irq_TimerCh0, Irq_RTI, Irq_IRQ, Irq_XIRQ, Irq_SWI, Irq_Illop, Irq_COPFail, sida 22
; Irq_ClockFail, _start Kod 13 Här har vi nu via direktivet #pragma DATA vectors deklarerat ett minnessegment med namnet vectors med pekare till avbrottsrutinerna och vi skall då strax se till att detta segment placeras i minnet på sådant sätt att avbrottsvektorerna hamnar på rätt adresser. Listan är nu gjord så att alla avbrottsvektorer är placerade i den ordning de förekommer i minneskartan och därmed hamnar på rätt adresser om vi placerar listan med start vi korrekt adress. Vi har dessutom sett till att fylla ut de reserverade positioner som finns i tabellen för att inte störa efterföljande vektorers offset. För att ta hand om oinitierade avbrott placerar vi en definitionslista före denna tabell, en lista som omdirigerar alla oanvända avbrott till default-rutinen Generic_Handler, en rutin som inte gör något men som vi också lägger in i koden. Denna default-rutin kommer dock i de flesta fall inte att ta hand om oinitierade avbrott på ett bra sätt eftersom de flesta avbrott måste bekräftas för att inte omedelbart uppträda igen. Det är denna rutin som i MC12 innehåller skrivning till terminal av ett meddelande som anger att vi har använt ett oinitierat avbrott. Vi får Kod 14 #define Irq_PWMEShutdown Generic_Handler #define Irq_PortPInt Generic_Handler #define Irq_MSCAN4Tx Generic_Handler #define Irq_MSCAN4Rx Generic_Handler #define Irq_MSCAN4Errs Generic_Handler #define Irq_MSCAN4WakeUp Generic_Handler #define Irq_BFGen Generic_Handler #define Irq_BFSync Generic_Handler #define Irq_BFRec Generic_Handler #define Irq_BFRx Generic_Handler #define Irq_MSCAN1Tx Generic_Handler #define Irq_MSCAN1Rx Generic_Handler #define Irq_MSCAN1Errs Generic_Handler #define Irq_MSCAN1WakeUp Generic_Handler #define Irq_MSCAN0Tx Generic_Handler #define Irq_MSCAN0Rx Generic_Handler #define Irq_MSCAN0Errs Generic_Handler #define Irq_MSCAN0WakeUp Generic_Handler #define Irq_Flash Generic_Handler #define Irq_EEPROM Generic_Handler #define Irq_SPI2 Generic_Handler #define Irq_SPI1 Generic_Handler #define Irq_IICBus Generic_Handler #define Irq_BDLC Generic_Handler #define Irq_SCMEVect Generic_Handler #define Irq_CRGLock Generic_Handler #define Irq_PACCBOv Generic_Handler #define Irq_ModDnCtr Generic_Handler sida 23
#define Irq_PortHInt Generic_Handler #define Irq_PortJInt Generic_Handler #define Irq_ATD1 Generic_Handler #define Irq_ATD0 Generic_Handler #define Irq_SCI1 Generic_Handler #define Irq_SCI0 Generic_Handler #define Irq_SPI0 Generic_Handler #define Irq_PACCAEdge Generic_Handler #define Irq_PACCAOv Generic_Handler #define Irq_TimerOv Generic_Handler #define Irq_TimerCh7 Generic_Handler #define Irq_TimerCh6 Generic_Handler #define Irq_TimerCh5 Generic_Handler #define Irq_TimerCh4 Generic_Handler #define Irq_TimerCh3 Generic_Handler #define Irq_TimerCh2 Generic_Handler #define Irq_TimerCh1 Generic_Handler #define Irq_TimerCh0 Generic_Handler //#define Irq_RTI Generic_Handler #define Irq_IRQ Generic_Handler #define Irq_XIRQ Generic_Handler #define Irq_SWI Generic_Handler #define Irq_Illop Generic_Handler #define Irq_COPFail Generic_Handler #define Irq_ClockFail Generic_Handler interrupt void Generic_Handler(void) // do nothing here... Kod 14 Lägg märke till att vi i #define-listan har anmärkt bort omdirigeringen av Irq_RTI. Eftersom vi skall använda detta avbrott så skall vi ju här i stället gå till den verkliga avbrottsrutin som vi har skrivit ovan. För att detta skall fungera så måste vi se till att vår avbrottsrutin har samma namn som finns angivet i avbrottstabellen, dvs här Irq_RTI. Lägg också märke till att vi behåller hopp till labeln start vid Reset och inte skriver över denna med #define-listan så resetvektorn finns inte med i denna lista utan listan slutar omedelbart före denna adress. Vi behåller skrivningen till FLASH-minnet för att göra detta minne läsbart som vi hade i föregående program men vår totala avbrottsvektortabell gör att vi skall ta bort skrivningen till de enskilda avbrottsvektorerna via #pragma-direktiv. Hela programmet, general_irq_flash.c, presenteras i Bilaga 23. Vi måste nu dessutom se till att datasegmentet vectors hamnar på rätt ställe i minnet, dvs på avbrottsvektorernas plats. Vi måste göra förändringar i skriptfilen till länkaren. Låt oss nu kalla filen general_irq_flash_script.lsc. Vi får alltså skapa en grupp som innehåller detta segment och placera den på rätt ställe i layouten. Vi får Kod 15 sida 24
-M Kod 15 group( c, const_group ) abs group( r, ram_group) bss group( r, flash_group) startupseg, text, cdata, data, group( r, unsec_group) UNSEC_address group( r, interrupt_vectors ) vectors layout 0x1000,0x3FFF <= ram_group, 0xC000,0xFEFF <= flash_group, 0xFF0E,0xFF0F <= unsec_group, 0xFF80,0xFFFF <= interrupt_vectors Den kompletta kommenterade skriptfilen, general_irq_flash_script.lsc, finns i Bilaga 24. Konfigureringen blir som i Figur 1 med den skillnaden att vi får byta till skriptfilen general_irq_flash_script.lsc för länkaren. Den resulterande mapfilen, general_irq_flash_debug.map, visas i Bilaga 25. Jämför vi med föregående mapfil så har de två segmenten innehållande avbrottsvektorerna för RTI- och RESET-avbrott försvunnit och ersatts av ett segment för vår kompletta avbrottsvektortabell. Vi ser också att vår avbrottsvektortabell i sig inte ger upphov till några globala symboler utan de enda avbrottssymboler som finns med är namnet på de avbrottsrutiner som används, dvs _Generic_Handler och _Irq_RTI. sida 25
Program i RAM-minne som innehåller avbrott Vi skall nu hitta en metod att låta program som ligger i RAM-minne använda avbrott trots att våra avbrottsvektorer ligger i FLASH-minne och inte direkt kan ingå i våra RAM-baserade program. Vi måste hitta en metod att dirigera om, relokera, avbrottsvektorerna så att vi kan nå dem via adresser i RAM-minne i stället för att adressera dem direkt i FLASH-minne. Det finns ingen möjlighet att konfigurera processorn att titta efter avbrottsvektorerna på några andra positioner i minnet än de positioner där de verkligen ligger utan vad vi får göra är att skriva ett program som gör att ett avbrott medför att programmet efter att ha läst aktuell avbrottsvektor hoppar till en adress i RAM-minnet. Detta kan vi bara göra genom ett program som placeras i FLASH-minnet vilket betyder vi kommer inte undan att programmera FLASH-minne men om vi skriver vårt program generellt, för alla olika avbrott, så räcker det att ladda ner detta program till FLASH-minne en enda gång varefter vi sedan kan använda detta program tillsammans med alla våra RAM-baserade applikationer. Lägg märke till att denna relokering är ett projekt för sig, en fristående applikation i FLASH-minne, även om den som vi skall se nedan saknar huvudprogram, och den är alltså inte direkt kopplad till den applikation vi sedan avser att placera i RAM-minne. Program för att relokera avbrottsvektorer till RAM-minne Vi måste alltså först skapa ett program i FLASH-minne som gör att vi vid avbrott får ett hopp till aktuell avbrottsrutin i RAM-minne. Detta program i FLASH-minne skall aldrig köras separat utan bara innehålla dessa stödrutiner för relokering av avbrottsvektorerna. Det betyder att vi inte skall göra något komplett program utan bara lägga ner en avbrottsvekortabell samt de rutiner som behövs för relokeringen till FLASH-minne och det innebär att vi skriver inget huvudprogram main() och har inte heller med någon startup-rutin, dvs vi tar inte med någon startup-fil bland projektets filer. Funktionen hos vårt FLASH-program får då för varje typ av avbrott bli sådan att vi på aktuell plats i den verkliga avbrottstabellen inte skriver adressen till aktuell avbrottsrutin, den rutinen finns ju i RAM-minne och vi vet ju faktiskt i nuläget inte dess startadress, utan i avbrottsvektortabellen skriver vi i stället adressen till en rutin i FLASH-minne som skall se till att vi går till rätt adress i RAM-minnet vid avbrottet och där hitta aktuell avbrottsrutin. Denna relokeringsrutin utformas då lämpligen inte så att vi hoppar till en specifik adress i RAM-minnet där vår avbrottsrutin finns eftersom detta skulle kräva att vi allokerar en fix del av RAM-minnet för varje typ av avbrott. Vi skulle få ett mycket oflexibelt program eftersom det skulle leda till att vi reserverar ett fixt antal minnesadresser för varje avbrottsrutin vilket betyder att en lång avbrottsrutin kanske inte får plats samtidigt som en kort rutin eller ingen rutin leder till tomma minnespositioner. Vi skall i stället utnyttja en instruktion som gör att vi går till en fast adress i RAM-minnet för varje avbrott och från den adressen läser adressen till den aktuella avbrottsrutinen, ungefär på samma sätt som vi vid avbrottet går till en fast adress i FLASH-minne (i avbrottsvektortabellen) och där läser adressen till avbrottsrutinen. Vi använder en form av pekare. Det är alltså bara dessa positioner i RAM-minnet för avbrottsrutinadresser som alltid behöver vara med i vår applikation. Den rutin i FLASH-minne som aktuell avbrottsvektor skall peka på skall då ha följande utseende Kod 16 sida 26