You are on page 1of 15

INTRODUCTION

C++ imposes restrictions on interrupts that make embedding them in classes


without breaking the encapsulation of the data members difficult. In this session, we will
explore the prohibitive, but logical, restriction C++ imposes on interrupt service routines
and a technique that works around this restriction. Additionally, we will automate this
technique so that our ISRs will automatically have access to private data members.
We will establish the following three requirements for the ISR enabled object:
1. The ISR must be a member function.
2. The encapsulation of the private data members of the object must not be broken.
3. The entire process must be automatic. By this I mean that the process must be
transparent to the user and the rest of the program. This is required so that dynamic object
creation based on hardware configuration can be supported.
Before we can explore the techniques used to embed ISRs into objects, we will
need to understand two related and key concepts, the this pointer and static member
functions. For those of you who are intimately familiar with these topics, you can skim
over the following review and jump right into the discussion of the details of the
technique.
THE THIS POINTER
In C++ when we instantiate an object of a certain class, memory is set aside for the
data associated with that object, i.e. its data members. Additionally, memory is allocated
for the objects member functions, (methods). When a second object is instantiated of the
same class, memory once again is allocated for the second objects data members, but no
additional memory is allocated for the member functions. In other words, each object has
its own data, but each object shares the member functions associated with that class. We
can see this demonstrated in the example code below.
class X
{
public:
void setX(int);
private:
int X;
};
void X::setX(int newX)
{
X = newX;
}
void main(void)
{
X object1, object2, object3;
object1.setX(1);
object2.setX(2);
object3.setX(3);
}
In the code above since object1, object2 and object3 are of type class X, they
each have an X variable, but object1, object2 and object3 each share the function setX.
Consider the member function call; object1.setX(1). If each object shares the member
function setX, how does the value 1 get assigned to object1s X variable? Each non-
static member function call has a this pointer to the calling object implicitly passed to it. In
other words, non-static member functions, such as setX(), have an extra parameter passed
to it when the function call is made. Since you did not provide the argument (explicitly) it
is implied by the function call itself. This extra parameter is a const (non-modifiable)
pointer to the object that is calling the shared function. This way when the function call is
executed the extra parameter (the this pointer) enables the function to know which
object should have its variable manipulated. For example, the setX function as we wrote
it looks like this:
void setX(int newX)
{
X = newX;
}
the code the compiler actually generates is roughly:
void setX(X* const this, int newX)
{
this->X = newX;
}
The function call we wrote was:
object1.setX(1);
The compiler generates something like this:
object1.setX(&object1, 1);
It is the implicit use of the this pointer that provides the mechanism that enables member
functions to work on the correct object.
3. STATIC MEMBER FUNCTIONS
Data members that are declared static are said to be class wide. This means that
this type of data member is not associated to, or belongs to, any one instantiated object of
a class. The classic example of the use of a static data member is a static count variable.
The static count variable is typically used to keep track of the number of objects, of a
certain class, that have been instantiated. Each objects constructor increments this
variable and each objects destructor decrements the variable. The static variable itself,
unlike non-static data members, belongs to the class not to any one object. Data members
declared as static have one other unique aspect, namely that they exist even though no
objects of that class have been instantiated. These variables can be, and are, accessed when
no objects exist. To access these variables when no objects have been instantiated, a static
member function, which also exists when no objects exist, is needed.
The caveat to all of this is that, since static member functions are also class wide,
and are mainly designed to access static data members, they do not have a this pointer
implicitly passed to them. Why doesnt a static member function get a this pointer, simply
put, it has no need for one. The static data members are class wide, therefore static
member functions dont need a this pointer to access these variables. This also means that
static member functions are limited to accessing static data members. They cannot,
because they dont have a this pointer, access non-static data. As seen above, without a
this pointer the function would have no way of knowing which object it was supposed to
work on.
4. THE PROBLEM
What does the above discussion concerning the this pointer and static member
functions have to do with ISRs? Unfortunately everything. The C++ language requires
that member functions declared with the key word interrupt must be declared as static
member functions. Why are functions declared with the key word interrupt required to be
declared static? During normal program execution a member function is called by an
object, for example consider the function call object1.setX(). In this call to the member
function setX(), the object that called it is object1. Now, lets imagine that we have
instantiated four objects of an interrupt class. When the hardware generates an interrupt,
the ISR that should serve that interrupt will get called asynchronously to the program flow
by the system. This means that since the system is calling the ISR, there isnt, so to speak,
a calling object. Without a calling object, the system is unable to know which of the four
this pointers, from the four interrupt objects we instantiated, to pass to the ISR.
Therefore, among other reasons, functions that are declared with the key word interrupt
are required to be static. The code snippet below demonstrates this problem:
class Registration
{
public:
static void interrupt isr(...);
private:
int variableTheIsrNeeds;
};
void interrupt Registration::isr(...)
{
int port = 0;
variableTheIsrNeeds = inport(port); // error member: variableTheIsrNeeds
// cannot be used without an object
}
As can be seen, the ISR member function cannot access the private data member
variableMyIsrNeeds. An ISR that cant access private data members is not of much use.
There are many ways to work around this problem, such as; dont use the keyword
interrupt, use a friend function, use a global pointer, use the binary scope resolution
operator, etc. All of these techniques exhibit a major weakness. They either require
knowing at compile time the address of the object, i.e. no dynamic creation of objects
based on hardware configuration or they take a significant step towards breaking the
encapsulation of the private data members within the given object.
5. A SOLUTION
A solution then, is to somehow provide the static ISR member function with a
pointer to the object that it is a member of. In other words a this pointer. Well if C++ isnt
going to implicitly pass the static ISR member function a this pointer, we will simply have
to steal it. To automate the process, which, we will remember, is one of the requirements
of the class, we will use the objects constructor. In the class that we are building, in
addition to the other necessary data members, we will declare a pointer to the class and we
will declare the pointer as a static data member
class Registration
{
public:
Registration();
static void interrupt isr(...);
private:
int variableTheIsrNeeds;
static Registration* ourThisPtr; // static pointer to the class
};
In the constructor, among other things, we can assign our static pointer the value of the
this pointer.
Registration::Registration()
{
ourThisPtr = this; //steal the this pointer
}
What we have done is when the constructor for the class gets called, our static
pointer will automatically be assigned the value of the this pointer. Therefore, when the
ISR gets called by the system, the ISR, although it is not passed a this pointer, will have
access to a this pointer. As can be seen, the advantage of using the constructor to
perform the assignment is that if the object is not created at compile time, but instead at
run time, i.e. dynamically,. the assignment is performed automatically by the system
completely transparent to the user and the rest of the program. Then in the interrupt we
can use our this pointer to access the private data members as the example code below
demonstrates:
void interrupt Registration::isr(...)
{
int port = 0;
ourThisPtr->variableTheIsrNeeds = inport(port);
}
6. REAL WORLD APPLICATIONS
There are basically two types of interrupt hardware systems. The first system
generates an individually vectored interrupt for each external interrupt signal. The second
system, generates only one universal interrupt and also produces a code, which is stored in
a register, to indicate which interrupt has occurred. In this second system, the software is
interrupted and vectored to the universal ISR, the register must then be decoded to
determine which interrupt has occurred and then the appropriate function is then called.
We will examine the individually vectored interrupt system first and then take a look at
what modifications would be required for the universal interrupt system.
For the individually vectored interrupt system, the technique outlined so far works
well. For each device that uses an interrupt, a class can be built that not only supports that
device, but also has an ISR as a member function. Then an object of that class can be
instantiated either at compile time or run time. It maybe tempting to develop an interrupt
class and inherit from it, but I would resist this temptation since the ISR will be a static
member function, and therefore class wide. If you did inherit from an interrupt class each
of the derived classes would inherit the sameISR function, in most cases this would be
unacceptable to say the least. Therefore, if a machine or device, that the program is being
developed for, requires two identical components, say two metering axes, then I would
recommend that two separate classes be created one called meter1 and the other meter2
each having their own unique ISR. Then in the program instantiate one object of each
class. Although this is somewhat redundant, it solves the problem of two different devices
sharing the same interrupt. The example program below demonstrates the individually
vectored technique described:
class MeterClass
{
public:
MeterClass();
static void interrupt MeterIsr(...);
int getVariable(void);
private:
int variableMeterIsrNeeds;
static MeterClass* meterThisPtr;
};
class FilmClass
{
public:
FilmClass();
static void interrupt FilmIsr(...);
int getVariable(void);
private:
int variableFilmIsrNeeds;
static FilmClass* filmThisPtr;
};
class InfeedClass
{
public:
InfeedClass();
static void interrupt InfeedIsr(...);
int getVariable(void);
private:
int variableInfeedIsrNeeds;
static InfeedClass* infeedThisPtr;
};
MeterClass::MeterClass()
{
variableMeterIsrNeeds = 0;
meterThisPtr = this;
}
void interrupt MeterClass::MeterIsr(...)
{
meterThisPtr->variableMeterIsrNeeds++;
}
int MeterClass::getVariable()
{
return variableMeterIsrNeeds;
}
FilmClass::FilmClass()
{
variableFilmIsrNeeds = 0;
filmThisPtr = this;
}
void interrupt FilmClass::FilmIsr(...)
{
filmThisPtr->variableFilmIsrNeeds++;
}
int FilmClass::getVariable()
{
return variableFilmIsrNeeds;
}
InfeedClass::InfeedClass()
{
variableInfeedIsrNeeds = 0;
infeedThisPtr = this;
}
void interrupt InfeedClass::InfeedIsr(...)
{
infeedThisPtr->variableInfeedIsrNeeds++;
}
int InfeedClass::getVariable()
{
return variableInfeedIsrNeeds;
}
void _interrupt _far (*oldIsr)(...);
// static data member must be defined
MeterClass* MeterClass::meterThisPtr = {NULL};
FilmClass* FilmClass::filmThisPtr = {NULL};
InfeedClass* InfeedClass::infeedThisPtr = {NULL};
void interrupt InterruptHandler(...)
{
union REGS regs;
char interruptNumber;
// the rand is used to simulate random hardware interrupts
interruptNumber = (rand() % 4) + 0x80;
int86(interruptNumber, &regs, &regs);
}
void main(void)
{
clrscr();
randomize();
oldIsr = _dos_getvect(0x1C);
_dos_setvect(0x80, MeterClass::MeterIsr);
_dos_setvect(0x81, FilmClass::FilmIsr);
_dos_setvect(0x83, InfeedClass::InfeedIsr);
_dos_setvect(0x1C, InterruptHandler);
// instantiate one object from each interrupt enabled class
MeterClass meterAxis;
FilmClass filmAxis;
InfeedClass infeedAxis;
cout << "The initial value of each axis' variable:" << endl
<< "Meter Axis: " << meterAxis.getVariable() << endl
<< "Flim Axis: " << filmAxis.getVariable() << endl
<< "Infeed Axis: " << infeedAxis.getVariable() << endl << endl;
delay(5000);
cout << "The ending value of each axis' variable:" << endl
<< "Meter Axis: " << meterAxis.getVariable() << endl
<< "Flim Axis: " << filmAxis.getVariable() << endl
<< "Infeed Axis: " << infeedAxis.getVariable() << endl << endl;
cout << "Since the delay was 5000 mS and each interrupt occurs every 55mS" <<
endl
<< "5000 / 55 equals 91" << endl << endl
<< "Therefore the outputs should add up to 91: "
<< meterAxis.getVariable() + filmAxis.getVariable()
+ infeedAxis.getVariable() << endl << endl;
cout << "Press any key to end"; // stop the program to see the output
getch();
// reset the old interrupt vector - play nice with the other children....!
_dos_setvect(0x1C, oldIsr);
return;
}
For systems that generate a universal interrupt, the technique discussed must be
modified. In an universal interrupt system it makes sense to develop an interrupt class and
inherit from it, since in reality there is only one ISR. This object would only contain the
members necessary to properly handle (vector) the interrupt as illustrated below:
class InterruptClass
{
public:
static void interrupt InterruptHandler(...);
virtual void Isr(void) = 0; // see discussion below on speed penalty
protected:
static InterruptClass* ourThisPtr[MAX];
static int index;
};
This base class would not contain the code of each of the hardware devices. Since this
universal interrupt class is an abstract class, it contains a pure virtual function, it would not
(actually it cant) be instantiated. From this base class we will derive and instantiate classes
for each hardware device. These hardware classes would contain all the code to control
and setup that device, including the specific function to handle the vectored interrupt.
class MeterClass : public InterruptClass
{
public:
MeterClass();
virtual void Isr(void); // see discussion below on speed penalty
int getVariable(void);
private:
int variableMeterIsrNeeds;
};
When the interrupt occurs, the InterruptHandler, which is embedded in the universal
interrupt object, is called and performs the decoding necessary to determine which
external hardware interrupt generated the universal interrupt. Once the specific interrupt is
determined, the ISR for that interrupt can then be called.
void interrupt InterruptClass::InterruptHandler(...)
{
int currentInterrupt;
currentInterrupt = rand() % index; // the rand is used to simulate random
interrupts
ourThisPtr[currentInterrupt]->Isr(); // call the correct ISR
}
As each derived object is instantiated, either dynamically or statically, their constructor
automatically registers their ISR with the base classs static interrupt member function.
MeterClass::MeterClass()
{
variableMeterIsrNeeds = 0;
ourThisPtr[index] = this; // automatic registration with base class
index++;
}
The example program below demonstrated the universal interrupt technique
#define MAX 16
class InterruptClass
{
public:
static void interrupt InterruptHandler(...);
virtual void Isr(void) = 0;
protected:
static InterruptClass* ourThisPtr[MAX];
static int index;
};
class MeterClass : public InterruptClass
{
public:
MeterClass();
virtual void Isr(void);
int getVariable(void);
private:
int variableMeterIsrNeeds;
};
class FilmClass : public InterruptClass
{
public:
FilmClass();
virtual void Isr(void);
int getVariable(void);
private:
int variableFilmIsrNeeds;
};
class InfeedClass : public InterruptClass
{
public:
InfeedClass();
virtual void Isr(void);
int getVariable(void);
private:
int variableInfeedIsrNeeds;
};
void interrupt InterruptClass::InterruptHandler(...)
{
int currentInterrupt;
// the rand is used to simulate random hardware interrupts
// you would need to determine which interrupt generated the
// universal interrupt
currentInterrupt = rand() % index;
// call the correct isr
ourThisPtr[currentInterrupt]->Isr();
}
MeterClass::MeterClass()
{
variableMeterIsrNeeds = 0;
ourThisPtr[index] = this; // automatic registration with base class
index++;
}
void MeterClass::Isr(void)
{
variableMeterIsrNeeds++;
}
int MeterClass::getVariable()
{
return variableMeterIsrNeeds;
}
FilmClass::FilmClass()
{
variableFilmIsrNeeds = 0;
ourThisPtr[index] = this; // automatic registration with base class
index++;
}
void FilmClass::Isr(void)
{
variableFilmIsrNeeds++;
}
int FilmClass::getVariable()
{
return variableFilmIsrNeeds;
}
InfeedClass::InfeedClass()
{
variableInfeedIsrNeeds = 0;
ourThisPtr[index] = this; // automatic registration with base class
index++;
}
void InfeedClass::Isr(void)
{
variableInfeedIsrNeeds++;
}
int InfeedClass::getVariable()
{
return variableInfeedIsrNeeds;
}
void _interrupt _far (*oldIsr)(...);
// static data member must be defined
InterruptClass* InterruptClass::ourThisPtr[MAX] = {NULL};
int InterruptClass::index = 0;
void main(void)
{
clrscr();
randomize();
oldIsr = _dos_getvect(0x1C);
_dos_setvect(0x1C, InterruptClass::InterruptHandler);
// once again instantiate one object of each class
MeterClass meterAxis;
FilmClass filmAxis;
InfeedClass infeedAxis;
cout << "The initial value of each axis' variable:" << endl
<< "Meter Axis: " << meterAxis.getVariable() << endl
<< "Flim Axis: " << filmAxis.getVariable() << endl
<< "Infeed Axis: " << infeedAxis.getVariable() << endl << endl;
delay(5000);
cout << "The ending value of each axis' variable:" << endl
<< "Meter Axis: " << meterAxis.getVariable() << endl
<< "Flim Axis: " << filmAxis.getVariable() << endl
<< "Infeed Axis: " << infeedAxis.getVariable() << endl << endl;
cout << "Since the delay was 5000 mS and each interrupt occurs every 55mS" <<
endl
<< "5000 / 55 equals 91" << endl << endl
<< "Therefore the outputs should add up to 91: "
<< meterAxis.getVariable() + filmAxis.getVariable()
+ infeedAxis.getVariable() << endl << endl;
// stop the program to see the output
cout << "Press any key to end";
getch();
// reset the old interrupt vector
_dos_setvect(0x1C, oldIsr);
return;
}
There is a speed penalty when using virtual functions, but seeing that the universal
interrupt technique only uses one virtual function, the speed penalty is slight. Like
always you will need to determine if your system can tolerate this small speed
penalty.
CONCLUSION
In this session we have explored the limitations that C++ places on ISRs and
examined two techniques to circumvent these limitations. As with any technique, it
cannot be expected to work for all embedded systems, and therefore, you the
designer will need to evaluate these techniques and determine if the advantages and
benefits they present outweigh the trade-offs.

You might also like