Файл: Interfacing with C plus plus-programing communication with microcontrolers (K. Bentley, 2006).pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 14.06.2025
Просмотров: 3580
Скачиваний: 1
122 6 DIGITAL-TO-ANALOG CONVERSION
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();
6 DIGITAL-TO-ANALOG CONVERSION 123
~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.
Instantiating DAC objects (calling constructors)
The question to consider is “Can we use this class?” To instantiate an object of type DAC, 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 programmergenerated 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
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