Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Interfacing with C plus plus-programing communication with microcontrolers (K. Bentley, 2006)

.pdf
Скачиваний:
192
Добавлен:
12.08.2013
Размер:
3.18 Mб
Скачать

124 6 DIGITAL-TO-ANALOG CONVERSION

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 improved DAC 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.

};

6 DIGITAL-TO-ANALOG CONVERSION 125

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 constructor DAC() 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 derived DAC 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

126 6 DIGITAL-TO-ANALOG CONVERSION

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

Derived Class

Derived Class

Inherited Base Class Members

Inherited Base Class’s Members

Parallel Port

Parallel Port

private

private

BaseAddress

BaseAddress

InDataPort1

InDataPort1

public

public

ParallelPort ( )

ParallelPort ( )

ParallelPort (baseaddress)

ParallelPort (baseaddress)

.

.

.

.

Derived Class Members

Derived Class Members

DAC

DAC

public

public

DAC ( )

DAC ( )

DAC (baseaddress)

DAC (baseaddress)

Case (a)

Case (b)

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

6 DIGITAL-TO-ANALOG CONVERSION 127

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);

128 6 DIGITAL-TO-ANALOG CONVERSION

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()

{

6 DIGITAL-TO-ANALOG CONVERSION 129

}

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);

}

6.5 Adding Members to Derived Classes

Having considered a simple class definition to understand the principles of inheritance and the derivation of new classes, we can now proceed to add extra functionality to the derived class. In the next example program, the DAC class has had a data member and a member function of its own added. The data member will remember the data last output to the DAC. The member function provides an interface to the outside world, allowing any function to query the last value output to the DAC. The DAC class will have a modified version of the inherited function WritePort0() to enable it to store the last output value. We will now see how to carry out the following:

1. Add new members to a derived class.

130 6 DIGITAL-TO-ANALOG CONVERSION

2. Modify an inherited function.

The new DAC class definition and its member function definitions are given in Listing 6-7. Note that in this listing we have re-declared the function WritePort0() as a member function of the DAC class. The C++ language requires us to do this so the body of the inherited function WritePort0() can be changed to suit our requirements. See Section 6.5.2 for additional details.

Listing 6-7 The DAC class with new members added.

class DAC : public ParallelPort

{

private:

unsigned char LastOutput;

public:

DAC();

DAC(int baseaddress);

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

};

DAC::DAC()

{

LastOutput = 0;

}

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

{

LastOutput = 0;

}

void DAC::WritePort0(unsigned char data)

{

outportb(BaseAddress,data); // Will not work! LastOutput = data;

}

unsigned char DAC::GetLastOutput()

{

return LastOutput;

}

The private data member LastOutput is added to the derived class DAC for the purpose of storing the value output by the WritePort0() function. A member

6 DIGITAL-TO-ANALOG CONVERSION 131

function named GetLastOutput() is also added to the derived class. It returns the value stored in LastOutput. Therefore, the only statement within the body of the GetLastOutput() function is:

return LastOutput;

Any function in the program that requires the value of the last data number output to the DAC (from the port at address BASE) must call the DAC class’s public function GetLastOutput(). This is the only way to access the value stored in the private data member LastOutput and ensures that functions outside the DAC class have no direct access to it. Once again, this demonstrates the controlled access to private data of a class by an object-oriented program.

Both constructors have been modified to initialise LastOutput to 0 by including the statement:

LastOutput = 0;

Therefore, at the time of instantiating a DAC object, the constructor will initialise the new data member LastOutput to 0.

We have modified the WritePort0() function in order to store the latest value output to the DAC by adding the line:

LastOutput = data;

However, the definition of the WritePort0() function given in Listing 6-7 will fail to compile. This is because the WritePort0() function of the derived class DAC is trying to use the inherited data member BaseAddress which is private to the base class. Although the private data of the base class is inherited, the derived classes cannot access this private data as shown in Figure 6-15.

One means to allow access to BaseAddress is by relaxing its access attributes. This can be done by declaring BaseAddress in the base class with protected access. Then BaseAddress can be accessed by all functions of all derived classes provided the classes are derived using a public or protected base class access specifier. Access specifiers are described in more detail ahead in section 6.5.1. The modified class definition of the base class is given in Listing 6-8.

NOTE

Declare variables as private unless you plan to derive other classes using the

current class as a base class. When you want to use the current class as a base class to derive new classes, carefully determine the variables of the current class you would want the derived class to have access to. Declare only these variables

as protected in the current class.

132 6 DIGITAL-TO-ANALOG CONVERSION

Derived Class

Derived Class

Inherited Base Class Members

Inherited Base Class’s Members

Parallel Port

Parallel Port

private

protected

BaseAddress

BaseAddress

InDataPort1

private

public

InDataPort1

ParallelPort ( )

public

.

ParallelPort ( )

.

.

.

.

Derived Class Members

Derived Class Members

DAC

DAC

private

private

LastOutput

LastOutput

public

public

DAC ( )

DAC ( )

DAC (baseaddress)

DAC (baseaddress)

WritePort0 ( )

WritePort0 ( )

GetLastOutput ( )

GetLastOutput ( )

Case (a)

Case (b)

Figure 6-15 Private and protected access specifiers.

Listing 6-8 Base class ParallelPort - BaseAddress as protected data member.

class ParallelPort

{

protected:

unsigned int BaseAddress;

private:

unsigned char InDataPort1;

public:

ParallelPort(); ParallelPort(int baseaddress);

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

};

6 DIGITAL-TO-ANALOG CONVERSION 133

class DAC : public ParallelPort

{

private:

unsigned char LastOutput;

public:

DAC();

DAC(int baseaddress);

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

};

A program that uses the class definition shown above is given in Listing 6-9.

6.5.1 Access specifiers

Consider the line:

class DAC : public ParallelPort

The keyword public in this line is an access specifier. Access specifiers change the access attributes as follows. The protected variables of the ParallelPort class can be accessed by the member functions of the DAC class, if and only if, the

DAC class is derived from ParallelPort using a public or protected base class access specifier. Access specifiers can also be private. Figure 6-16 shows how the access specifiers determine access attributes of inherited members.

Access Specifier public

When deriving a class using a public base class access specifier, all inherited public members of the base class will become public members of the derived class, all inherited protected members of the base class will become protected members of the derived class. All inherited private members will remain private to the base class, and so the derived class cannot access them.

Access Specifier protected

When deriving a class using a protected base class access specifier, all inherited public and protected members of the base class will become protected members of the derived class. All inherited private members will remain private to the base class, and so the derived class cannot access them.

Access Specifier private

When deriving a class using a private base class access specifier, all inherited public and protected members of the base class will become private members of the derived class. All inherited private members will remain private to the base class, and so the derived class cannot access them.