/***********************************************************************
MEMORIAL UNIVERSITY OF NEWFOUNDLAND
Faculty of Engineering and Applied Science
Engineering 3891(Advanced Programming)
Assignment #8 Instructor: Michael Bruce-Lockhart
Date: 00.11.24 Due: 00.12.04
***********************************************************************/
/* In this assignment, we are going to play around a little bit with how
you might use hierarchy and inheritance to represent electronic devices.
A WORD OF WARNING. This design essentially requires derived classes
to register their class with the base class. It happens that simplifies
a
number of problems; it also gives the design a very particular flavour.
It was not done for those reasons, but rather because the approach MAPS
NATURALLY ONTO THE UNDERLYING PROBLEM. Circuit simulators like SPICE
generally do type each device, using a single letter as we have done.
The warning? Simple. This is the only design involving inheritance you
will really see in this course. Later on, if you have to do your own,
don't use this one as a template. Fit your design to your problem.
*/
// Typically simulation programs distinguish between inputs, outputs and
// bidirectional (such as resistor) pins
enum PinType{IN,OUT,BIDI};
// Here we represent a network by a simple number (the node number of
// the net. 0 will be taken to mean ground, and any negative number
// represents no connection. Using a typedef her means we can easily redefine
// what we mean by a net later on if we want.
typedef int net;
/* Note that in C++, the ONLY difference between structures and classes
is
that the default access for structs is public, for classes, private.
Here I am using pin like a C structure except that I have built a
constructor which is useful for returning the structure from functions.
*/
struct Pin {
PinType t;
net n; // net number to which pin is connected.
Pin(PinType p=BIDI,int i = -1){t=p;n = i;} // A simple constructor
}; // to init struct
/* A generic base class for electronics devices. It contains a one letter
designator, an instance no. (to tell one device from another), the no.
of pins and an array of those pins.
In a CAD package used both for SCHEMATIC CAPTURE and for SIMULATION,
basic graphical information such as the (x,y) location of the device
could be bundled with the base class.
*/
class Device {
public:
// The constructor creates a device of n pins (where n > 1), with desig
// d and instance number i. The array of pins should be initialized
// to (BIDI,-1) - bidirectional, unconnected.
// In the event that a device cannot be constructed (e.g. insufficient
// memory for the pin array or an invalid arguement), a null device
// should be created (desig = '\0', instance = pins = 0;
Device(char d, int i, int n);
~Device(); // Kill off the pin array
// Overload of the iterator to allow reading or connection of the i'th
pin
// Thus the statement d1[2] = pin(in,4) sets pin no. 2 of device d1 to an
// input, connected to net #4.
// NOTE that pins are numbered from 1 to pins. In the event of an index
// being out of range, the routine should return a dummy pin that the user
// can safely overwrite.
Pin& operator[](int i);
int numPins() {return mNumPins;}
// The last function should not return a pointer to any part of a
// Device object since the user could use that to overwrite the object.
// Instead, it should create a static buffer and build a name there. The
name
// itself should consist of the designation character followed by the
// characters representing the instance no. It should be in the form of
// a proper string (i.e. null terminated). You may assume reference instance
// numbers will not exceed four figures. When the string is built in the
// buffer return a pointer to it. You might consider what problems there
are
// with this strategy.
// Example strings would be "R12" for the 12th resistor and "U189"
for the
// 189th digital gate (see comments under individual derived classes below).
// HINT: you are going to need the itoa() function, or something similar,
// from stdlib.h
char* who();
protected:
char mDesignator; // reference designator
int mInstance; // of that designator
int mNumPins; // No. of pins
Pin* mpPins; // Pin array - numbered 1 to pins
static Pin dummy; // A dummy pin that can be safe to write to if improper
reference
};
// Now, some derived classes.
class Simple:public Device { // The class of all two pin devices
public:
// Must construct a 2 pin device, designation d, instance i, value v and
// tolerance tl
Simple (char d,int i, double v=1.,double tl=0.);
double value(); // Return value
double tolerance(); // Return tolerance
void set(double v,double t); // Change value & tolerance
protected:
double mValue; // e.g. 1000 for a 1 KOhm resistor
double mTolerance; // e.g. .01 for a 1% resistor
};
/*
At last! - real devices. All of these classes use a static 'last' to
track the last device number ASSIGNED TO THAT CLASS. Thus 'last' might be
4 for capacitors, 7 for resistors, and 3 for gates. It is used by the
constructor to find the next instance of that device. For example, if a
new capacitor is constructed, its designator should be 'C' and its
instance should be 5 and 'last' for class capacitor
should be advanced to 5.
Note there is no destructor. If we destroy a capacitor, we will have
one less capacitor; nevertheless, the next capacitor will be assigned
the no. 6 since there is no guarantee that C5 was the one destroyed.
For capacitors, value is the value in farads, for resistors it is in
ohms.
Don't forget, you must define the static variables in your implementation.
They are only declared within the class (no space is set aside for them).
This is because there is only 1 of a static variable for every object of
the class. i.e. all resistor objects share the same last. Normally, space
for member data is set aside only when an object of the class is created.
Static data must effectively pre-exist all objects of the class nor is it
tied to any one object of the class but shared by all. Thus space must
be independently put aside for it ("defined") when the class itself
is
defined (i.e. implemented). It may be initialized at the same time.
*/
enum CapType {CERAMIC,ELECTROLYTIC,TANTALUM};
class Capacitor:public Simple {
public:
Capacitor(double c=1e-12, CapType t=CERAMIC,double m = .1);
// Construct 2 pin device, designation 'C', instance last,
// of value c, capType t, tolerance m
CapType type();
private:
static int last; // The last capacitor number assigned
CapType mCapType;
};
class Resistor:public Simple {
public:
// Construct simple device, designation 'R', instance last,
// of value r, tolerance t
Resistor(double r=1e3,double t=.05);
private:
static int last; // The last resistor number assigned
};
/* Two input logic gates have three pins and are designated by a 'U'.
Again the instance should be the current value of last. Pins 1 & 2 are
always inputs while pin 3 is always the output.
*/
enum LogicType{OR,AND,NOR,NAND,XOR}; // What kind of logic function
class Gate:public Device {
public:
Gate(LogicType t=OR); // Need NULL constructor for new
LogicType logicType();
private:
static int last;
LogicType mLogicType;
};
/* When you've implemented all this stuff, you should be able to test a
resistor object for example by
Resistor R1(10e3,.05); // Construct a 10K, 5% resistor
R1[1].n = 4; // Connect pin 1 to net 4
R1[2].n = 0; // Connect pin 2 to ground
cout << R1.who();
This works because a resistor is a device and the Device class overloads
the operator[] to allow access to pins as well as providing a method for
retrieving (safely) the name of the resistor.
*/