© Harry Broeders.
Deze pagina is bestemd voor Technische Informatica studenten van de THRijswijk die de minor Elektrotechniek volgen.
In de vorige opdracht werd de tijd tussen het verschuiven van het patroontje bepaald door de volgende wachtlus.
void wait() {
volatile word i;
for (i=0; i<10000; ++i)
/*empty*/;
}
Het is moeilijk om op deze manier een exacte tijdvertraging te realiseren. Als je een bepaalde tijd wilt wachten kun je beter de real-time clock die in de 68HC11 is ingebouwd gebruiken.
RTR1
en RTR0
bits in
PACTL
register. (Klokfrequentie EVM = 8 MHz).
RTIF
in het TFLG2
register geset.
RTIF
bit kan gereset worden door er een 1
naar
toe te schrijven (raar maar waar)!
RTII
in TMSK2
register aanzetten.
Pas het programma uit opgave 1 zodanig aan dat het verschuiven van het patroontje
op de LED's wordt aangestuurd met behulp van een Real-Time Clock. Het patroontje
moet telkens na 500 ms opschuiven. Maak gebruik van polling (telkens wachten
tot RTIF
1
wordt).
Let op! Als je dit programma een andere naam wilt geven, bijvoorbeeld opdr2.c dan moet je een nieuw directory aanmaken en ook het linkerscript evm.ld en de makefile makefile in deze directory kopiëren. In de makefile moet je vervolgens de regel:
OBJECTS = opdr1.o
veranderen in:
OBJECTS = opdr2.o
In opdracht 2a heb je gebruik gemaakt van polling, dit wordt "busy waiting" genoemd. Je kunt op deze manier als je staat te wachten niets anders doen. Pas het programma zodanig aan dat het verschuiven van het patroontje op de LED's wordt aangestuurd met behulp van een Real-Time Clock interrupt. Het patroontje moet weer telkens na 500 ms opschuiven. Maak daarbij gebruik van de in de les behandelde voorbeeldprogramma.
typedef unsigned char byte;
volatile byte* tmsk2=(byte*)0x1024;
volatile byte* tflg2=(byte*)0x1025;
void rtti_isr(void) __attribute__((interrupt));
void rtti_isr(void) {
/* ... */
*tflg2=0x40;
}
int main() {
*tmsk2|=0x40;
/* ... */
return 0;
}
typedef void (*isr)(void);
#define E (isr)0xffff
#ifdef __cplusplus
extern "C" void _start(void);
#else
extern void _start(void);
#endif
isr vectors[32] __attribute__ ((section (".vector"))) = {
E, E, E, E, E, E, E, E,
E, E, E, E, E, E, E, E,
E, E, E, E, E, E, E, E,
rtti_isr, E, E, E, E, E, E, _start
};
Als de 68HC11 een interrupt krijgt dan wordt eerst de huidige instructie
afgemaakt en daarna worden alle registers op de stack geplaatst. Vervolgens
wordt naar de ISR (Interrupt Service Routine) gesprongen. Aan het
einde van de ISR moet een RTI
(ReTurn from Interrupt)
instructie worden gebruikt zodat alle registers weer van de stack worden
verwijderd.
De 68HC11 "weet" van tevoren niet wanneer de interrupt zal optreden. Op de een of andere manier moet de programmeur opgeven welke ISR uitgevoerd moet worden bij het optreden van een bepaalde interrupt. Omdat de programmeur ook niet van tevoren weet op welke plaats het programma onderbroken zal worden door een ISR kan de ISR niet worden aangeroepen met een "normale" functieaanroep.
In een zogenaamde interrupt vector tabel geeft de programmeur voor
elke gebruikte interrupt het adres van de ISR op (de plaats waar het adres
van de ISR moet worden ingevuld wordt de interrupt vector genoemd).
Bij de 68HC11 bevindt deze interrupt vector tabel zich in het geheugen op
de adressen $FFC0
t/m $FFFF
. In de documentatie
van de 68HC11 kun je vinden welk adres voor een bepaalde interrupt wordt
gebruikt.
Een ISR kan in C als volgt gedeclareerd worden:
void rtti_isr(void) __attribute__((interrupt));
Een ISR is dus een "soort" functie die aangeroepen wordt als de betreffende
interrupt optreed. Omdat je niet weet wanneer de interrupt zal optreden kun
je geen argumenten meegeven en ook geen return type gebruiken. De
__attribute__((interrupt))
is nodig
om de compiler te vertellen dat de functie moet eindigen met een
RTI
(ReTurn from Interrupt) instructie in plaats van met de
RTS
(ReTurn from Subroutine) instructie waarmee een gewone functie
eindigt.
In de interrupt vector tabel moet het startadres van de ISR worden ingevuld.
In C is dat een pointer naar de ISR functie. Om dit te kunnen doen
is het type isr
gedefinieerd als een pointer naar een functie
waaraan je niets meegeeft en die zelf ook niets teruggeeft:
typedef void (*isr)(void);
De interrupt vector tabel is in C niets anders dan een array gevuld met deze pointers:
isr vectors[32]
De __attribute__ ((section
(".vector")))
is
nodig om de array "te plaatsen" op de adressen $FFC0
t/m
$FFFF
.
Deze array wordt meteen geïnitialiseerd met de juiste waarden:
isr vectors[32] __attribute__ ((section (".vector"))) = {
E, E, E, E, E, E, E, E,
E, E, E, E, E, E, E, E,
E, E, E, E, E, E, E, E,
rtti_isr, E, E, E, E, E, E, _start
};
De naam van een functie (rtii_isr
) kan in C worden toegekend
aan een pointer naar een functie. Je zou ook &rtti_isr
mogen
gebruiken om aan te geven dat je het adres van de functie in de tabel invult.
In de documentatie van de 68HC11 kun je vinden dat de RTTI (Real Time Timer
Interrupt) de interrupt vector op adres $FFF0
en
$FFF1
gebruikt. Omdat de 68HC11 een 16 bits adresbus heeft en
dus 16 bits adressen gebruikt is een pointer (die een adres bevat) in C code
voor de 68HC11 ook 16 bits. Elke plaats in de array vectors neemt dus 16
bits (= 2 bytes) in gebruik. De waarde E
is gedefinieerd als:
#define E (isr)0xffff
Om aan te kunnen geven dat een interrupt vector leeg is.
Het is de bedoeling dat je zelf voor elke interrupt van de 68HC11 kunt bepalen op welke plaats in de array je de naam van de betreffende ISR moet invullen!
Door de reset vector te definiëren willen we het programma bij een harware
reset op het juiste adres laten (her)starten. We zouden het programma bij
een reset naar de functie main()
kunnen laten "springen". Dit
is echter onjuist! Voordat in een C of C++ programma de
functie main()
wordt aangeroepen moeten er al eerst allerlei
actie plaatsvinden. De stackpointer moet van de juiste waarde worden voorzien,
globale variabelen moeten worden geïnitialiseerd en in C++ moeten de
constructors van alle globale objecten worden aangeroepen. De code die moet
worden uitgevoerd voordat main()
kan beginnen wordt de opstartcode
genoemd. Deze opstartcode word automatisch door de GNU linker meegelinkt
met de rest van het programma. Aan het begin van de opstartcode wordt de
identifier (het label) _start
gedefinieerd. De opstart code
staat in de file C:\Program
Files\THRSim11\gcc\lib\gcc-lib\m6811-elf\3.3.5-m68hc1x-20050129\crt1.o
.
De inhoud van deze file kun je bekijken door in het command window van THRSim11
het volgende commando in te typen:
!objdump -x -S "C:\Program Files\THRSim11\gcc\lib\gcc-lib\m6811-elf\3.3.5-m68hc1x-20050129\crt1.o"
Als we het label _start
in de vector tabel zetten:
typedef void (*isr)(void);
#define E (isr)0xffffextern void _start(void);
isr vectors[32] __attribute__ ((section (".vector"))) = {
E, E, E, E, E, E, E, E,
E, E, E, E, E, E, E, E,
E, E, E, E, E, E, E, E,
rtti_isr, E, E, E, E, E, E, _start
};
Dan werkt dit goed onder C.
Als we dezelfde code als C++ code compileren krijgen we een linker fout:
"C:\Program Files\THRSim11"\gcc\bin\m6811-elf-gcc.exe -g -c -m68hc11 -Wall opdr1c.cpp "C:\Program Files\THRSim11"\gcc\bin\m6811-elf-gcc.exe -m68hc11 -T evm.ld opdr1c.o opdr1c.o: undefined reference to `_start()' collect2: ld returned 1 exit status
Dit komt doordat in C++
name-mangling
wordt gebruikt om function overloading (meerdere functies met dezelfde naam)
mogelijk te maken. De functie: void f(void)
krijgt door
name-mangling bijvoorbeeld de naam _Z1fv
. De overloaded functie
void _start(int)
bijvoorbeeld krijgt de naam _Z1fi
.
Op deze manier kan de linker deze 2 overloaded functies uit elkaar houden.
Je kunt de mangled names van een in THRSim11 geladen C++ programma zichtbaar
maken door in het command window van THRSim11 het volgende commando in te
typen:
!nm a.out
Je kunt de orginele namen zichtbaar maken met het commando:
!nm -C a.out
We kunnen dit probleem in C++ oplossen door de functie _start
als volgt te definiëren:
extern "C" void _start(void);
Deze syntax zorgt ervoor dat de name-mangling wordt uitgeschakeld en is speciaal bedoeld om C functies vanuit C++ aan te roepen. Maar deze syntax kun je weer niet in C gebruiken.
De oplossing is conditionele compilatie te gebruiken:
#ifdef __cplusplus
extern "C" void _start(void);
#else
extern void _start(void);
#endif