• No results found

Digital-to-Analog Conversion

6.4 Derivation of Object Classes

In the last example, we used the ParallelPort object class to operate the DAC. This object is designed for general-purpose use of the parallel port. The ParallelPort object class is more than capable of handling the simple requirements of the DAC. However, using the ParallelPort object class for digital-to-analog conversion is not particularly appropriate. Instead, it is desirable to create an object class, which at least has a name that suits digital-to-analog conversion. This is an ideal situation to derive a class. We are for now merely applying a change in name which will reduce the complexity associated with the derivation of the new class. As we progress through the book we will confront more involved class derivations.

One of the main strengths of object-oriented programming is the re-useability of the program segments developed in the past. The C++ language has excellent mechanisms in place to expand the capabilities of an existing class and thereby to form a new super class. As described in Chapter 4, these super classes are known asderived classes. To be able to derive a class, a base class must exist. The derived classes are meant for more specific purposes than the base class. In our case, the general-purpose object class ParallelPort can be considered as the base class. The new class to be created needs to be more specific to suit the Digital-to-Analog Converter. Listing 6-2 shows the simplest way to create the new class.

Listing 6-2 Derivation of DAC class.

class ParallelPort {

private:

unsigned int BaseAddress; unsigned char InDataPort1; public:

ParallelPort(int baseaddress); void WritePort0(unsigned char data); void WritePort2(unsigned char data); unsigned char ReadPort1();

};

class DAC : public ParallelPort {

};

The class derivation is kept simple so that we can direct our attention to the principles of derivation of classes rather than the actual functionality of the DAC object class. The class definition for the ParallelPort object class is identical to that given in Chapter 5. The only new part in the above listing is the class definition for the object type DAC shown in bold typeface. The first line of the new object class definition is:

class DAC : public ParallelPort

Here, we derive a new class named DAC, using the existing class ParallelPort as the base class. The keyword public before ParallelPort, is referred to as an access specifier. We will return to access specifiers after considering the member data and member functions we inherited from the base class.

Inherited members of the DAC class

Here, the word inheritance is used to describe the fact that the derived class inherits all the member data and member functions of the base class. If you now take a closer look at the DAC object class, you will see that the body of the DAC class definition is empty. It was explained in Chapter 5 that if we do not provide our own constructor and destructor for the object class, the compiler will provide them. Therefore, although not visible, the DAC class has a default constructor and a default destructor provided by the compiler. Also, because the DAC class has been derived from the ParallelPort class, it has inherited all the members (both data and functions including constructors) of the ParallelPort class.

If we list the data members of the DAC class, they are as follows:

unsigned int BaseAddress; unsigned char InDataPort1;

The member functions of the DAC class are as follows:

DAC(); // Compiler-generated constructor (hidden). ParallelPort();

ParallelPort(int baseaddress);

void WritePort0(unsigned char data); void WritePort2(unsigned char data); unsigned char ReadPort1();

~ParallelPort(); // Inherited compiler-generated // destructor (hidden).

~DAC(); // Compiler-generated destructor (hidden).

NOTE

Note that we did not include destructors in the class definition for the ParallelPort class and also in the class definition for the DAC class. Therefore,the compiler will generate a hidden default destructor for each of these classes as listed above. All compiler-generated constructors and destructors are described as hidden to the programmer because they are invisible in the source code. See Chapter 8 for a more detailed description of destructors.

The DAC class is equivalent to the ParallelPort class except that it has a default constructor and destructor provided by the compiler. It is clear that this particular class derivation is just a name changing exercise since we did not expand the capability of the DAC class. It can only do as much as the ParallelPort class and no more. However, if we need to enhance the power of the DAC class, it is an easy matter to add member data and member functions to the new DAC class in its class definition and then provide the definitions of the added functions.

InstantiatingDAC objects (calling constructors)

The question to consider is “Can we use this class?” To instantiate an object of typeDAC, we must call the constructor of the DAC class. It would be called in the declaration statement for the new object named D_to_A as shown below:

DAC D_to_A;

When the constructor of a derived class is first called, it will call the constructor of its inherited base class that takes whatever parameters it is being passed from the derived class constructor (this will be further explained ahead). The base class constructor instantiates its members and completes whatever tasks it has been coded to do. When it is finished it returns program execution back to the start of the body of the derived class constructor. The derived class constructor then instantiates its members and completes whatever tasks it has been coded to do. In the case of this particular program, the DAC class does not have a programmer- generated constructor, so the compiler adds its own default constructor (takes no parameters). This is the constructor that is called when the above statement is executed. Before it performs its own tasks, it will call its inherited base class constructor. Because the derived class constructor is not passing an argument to the constructor of the base class, the constructor ParallelPort() and not the constructor ParallelPort(int baseaddress) will be called to allocate memory for the inherited base class members. If you look at the body of this constructor in Listing 6-1 you will see that it also initialises BaseAddress to the

value 0x378, (and InDataPort1 to zero). Then the DAC class’s default constructor instantiates memory for whatever members are added to it (none) and executes its empty body.

The public function WritePort0() has been inherited from the ParallelPort class. Therefore, it can be used to send the data (in this case, 255) to the parallel port at the initialised base address value (0x378) using the following statement:

D_to_A.WritePort0(255);

There is a problem with this program – the DAC class does not allow the user to specify a value for the BASE address of the parallel port. This is inadequate for those users that have their parallel ports at a different BASE address than 0x378. Therefore, we need some mechanism to provide the option to initialise the data member variable BaseAddress to a suitable value. Normally the best way to do this is to use the constructor. Similar to the early stages when developing the ParallelPort object, the compiler-generated constructor for the DAC class is not adequate for our application. We must provide our own constructors and code them in a manner that allows us to specify the value of BaseAddress. An improvedDAC class is given in Listing 6-3.

Listing 6-3 An improved DAC class.

class ParallelPort {

private:

unsigned int BaseAddress; unsigned char InDataPort1; public:

ParallelPort();

ParallelPort(int baseaddress);

void WritePort0(unsigned char data); void WritePort2(unsigned char data); unsigned char ReadPort1();

};

class DAC : public ParallelPort {

public:

DAC(); // default constructor.

DAC(int baseaddress); // constructor. };

This time we have used a similar approach as for the ParallelPort class and declared two constructors for the DAC class. Note that the compiler will not provide a default constructor because we have provided the constructor DAC(int baseaddress) shown in Listing 6-3. Therefore, we must provide our own default constructor DAC().

We can now define these constructors by providing their statements which will set the parallel port’s BASE address by initialising inherited private data member BaseAddress. The first attempt to define the constructors is given in Listing 6-4.

Listing 6-4 A failed attempt to define the constructor.

DAC::DAC() // Does work. {

}

DAC::DAC(int baseaddress) {

BaseAddress = baseaddress; // Fails to work! }

The following statement will use the DAC class’s programmer-generated default constructorDAC() to instantiate an object of type DAC named D_to_A:

DAC D_to_A;

This default constructor of the DAC class will operate in the same way that its compiler-generated default compiler did. It will first call the inherited base class constructor that also takes no arguments - the base class’s default constructor ParallelPort(). This constructor will instantiate the inherited base class members, initialise the private data member InDataPort1 to 0, and initialise the private data member BaseAddress to the value 0x378. Then the DAC class’s default constructor will be executed to instantiate whatever members have been added to it (none), followed by executing its body – in this case with nothing in it. The derived class DAC inherits the members of its base class ParallelPort but does not have access to the private members of its inherited base class (regardless of the acess specifier used – public in this case). This means that the constructor DAC(int baseaddress) shown in Listing 6-4 will not be able to access the inherited base class data member BaseAddress (declared as private). Therefore, it cannot be compiled and so cannot work!

The solution in this situation is as follows. Instead of a member function from the derivedDAC class trying to make an illegal attempt to directly access a private data member inherited from its base class ParallelPort, a call can be made to a public function inherited from the base class that can change the private data

memberBaseAddress of its class as shown in Figure 6-14. A proper constructor definition for the DAC class is given in Listing 6-5.

Figure 6-14 Accessing the inherited private data member BaseAddress.

Listing 6-5 Corrected definition of the constructor of the DAC class.

DAC::DAC() {

}

DAC::DAC(int baseaddress) : ParallelPort(baseaddress) {

}

The part-line of bold font code shown in Listing 6-5 is new and is the mechanism that allows the value of private data member BaseAddress to be set to the value of the argument passed at the time of instantiating the DAC object named D_to_A:

DAC D_to_A(0x3BC);

When the program encounters this statement, it calls the appropriate constructor from the DAC class. Because the parallel port’s BASE address is given as an argument when instantiating the D_to_A object, the constructor DAC(int

Parallel Port private BaseAddress InDataPort1 public ParallelPort ( ) ParallelPort (baseaddress) . . DAC public DAC ( ) DAC (baseaddress)

Derived Class Members

Case (a) Inherited Base Class Members

Derived Class

DAC public DAC ( )

DAC (baseaddress)

Derived Class Members

Case (b) Parallel Port private BaseAddress InDataPort1 public ParallelPort ( ) ParallelPort (baseaddress) . .

Inherited Base Class’s Members

baseaddress) will be called, and not the default constructor DAC(). When first called, the constructor calls the appropriate constructor of the inherited base class that takes the same arguments being passed to it. The bolded part-line:

DAC::DAC(int baseaddress) : ParallelPort(baseaddress)

informs the compiler that the derived class constructor DAC(int baseaddress) is to pass the parameter baseaddress to the inherited base class constructor ParallelPort(baseaddress). This base class constructor will instantiate its members and then initialise its private data member BaseAddress to be equal to the argument passed to the parameter baseaddress (and initialise InDataPort1 to zero). Immediately following this action, the constructor DAC(int baseadress) will be executed to instantiate its added members (none) and then complete its tasks contained within its empty body.

This is how to initialise the inaccessible private data member BaseAddress inherited from the ParallelPort class - with minimum code thanks to inheritance.

Now we can turn our attention to using this object class in a program to carry out digital-to-analog conversion. The complete program is shown in Listing 6-6. Instead of instantiating an object of type ParallelPort, this program instantiates an object of type DAC. The operation of the program is identical to the one shown in Listing 6-1.

Listing 6-6 The use of the DAC class for Digital-to-Analog Conversion.

/******************************************************* The new class DAC is used in the main() function to sequentially write several bytes of data to the Digital to Analog convertor. *******************************************************/ #include <iostream.h> #include <conio.h> class ParallelPort { private:

unsigned int BaseAddress; unsigned char InDataPort1; public:

ParallelPort();

ParallelPort(int baseaddress);

void WritePort0(unsigned char data); void WritePort2(unsigned char data);

unsigned char ReadPort1(); }; ParallelPort::ParallelPort() { BaseAddress = 0x378; InDataPort1 = 0; } ParallelPort::ParallelPort(int baseaddress) { BaseAddress = baseaddress; InDataPort1 = 0; }

void ParallelPort::WritePort0(unsigned char data) {

outportb(BaseAddress,data); }

void ParallelPort::WritePort2(unsigned char data) {

outportb(BaseAddress+2,data ^ 0x0B); }

unsigned char ParallelPort::ReadPort1() {

InDataPort1 = inportb(BaseAddress+1); // Invert most significant bit to compensate

// for internal inversion by printer port hardware. InDataPort1 ^= 0x80;

// Filter to clear unused data bits D0, D1 and D2 to zero. InDataPort1 &= 0xF8;

return InDataPort1;

}

class DAC : public ParallelPort { public: DAC(); DAC(int baseaddress); }; DAC::DAC() {

}

DAC::DAC(int baseaddress) : ParallelPort(baseaddress) {

}

void main() {

DAC D_to_A;

cout << "Press a key ... " << endl; getch();

D_to_A.WritePort0(0);

cout << "Press a key ... " << endl; getch();

D_to_A.WritePort0(32);

cout << "Press a key ... " << endl; getch();

D_to_A.WritePort0(64);

cout << "Press a key ... " << endl; getch();

D_to_A.WritePort0(128);

cout << "Press a key ... " << endl; getch();

D_to_A.WritePort0(255); }