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

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

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

5 OBJECT-ORIENTED PROGRAMMING 83

in-turn calls the familiar outportb() function to send data to the port at address given by BaseAddress.

We can have a default constructor generated automatically by the compiler if we do not write a constructor in the class definition.

C++

Syntax of member functions

 

 

There are two ways the compiler can be informed of member functions:

(i)Member function definition placed outside the class.

In this case the syntax used is the same as that for normal functions, except the object class name followed by two colons (::) is placed before the function name as shown in Listing 5-1.

(ii)Member function definition placed within the class.

In this case the syntax used in (i) is not necessary as shown in Listing 5-2.

The member function definition shown in Listing 5-1 for the WritePort0() function is defined outside the class.

Listing 5-1 Definition of the member function(s) for the ParallelPort class.

void ParallelPort::WritePort0(unsigned char data)

{

outportb(BaseAddress,data);

}

When the WritePort0() member function is defined within the class definition itself, the class definition will be as shown in Listing 5-2. Any member functions that are defined within the class definition and are small, will be treated as in-line functions (discussed ahead). When functions are declared outside the class definition, they will be treated as normal functions.

Listing 5-2 Defining member functions within the class definition.

#include <dos.h>

class ParallelPort

{

private:

unsigned int BaseAddress;

public:

void WritePort0(unsigned char data)

84 5 OBJECT-ORIENTED PROGRAMMING

{

outportb(BaseAddress,data);

}

};

C++

In-line member functions

 

 

The compiler allows small member functions defined in the class definition to be

in-line functions. Whenever an in-line function is called in a source file, the

compiler may replace that call with the actual instructions within the body of the

in-line function.This will happen if the in-line function is small in size and does

not involve complicated (lengthy) parameter passing.

This means every time an in-line function is called in the code, the compiler will

add a new instance of the function to the executable code which increases the

size of the program. However, there is a benefit that program execution flows directly into the in-line function, avoiding the overheads associated with a

function call and therefore promotes greater speed.

Normal functions use only one instance of the function stored in memory. In this

case, a function call results in the current program status being temporarily saved

before the program jumps to the memory area containing the function. The code

for the function is executed followed by a return jump and recovery of prior

program status. These extra steps of saving, jumping and recovering add

execution time, and slow performance compared with using in-line functions

(although the executable programs are smaller in size).

NOTE Listing 5-1 and Listing 5-2 both have a deficiency. Examining the parameters of the WritePort0() function, we see that we can pass an actual argument (such as 0x7F) in place of the parameter data, however, there is no way for us to specify the BaseAddress.

If we do not have a mechanism to specify a numerical value for BaseAddress, the outportb() function will not work. One poor solution is to fix the address at a predetermined value. For example:

void WritePort0(unsigned char data)

{

outportb(0x378,data);

}

The above approach will only work for those users having 0x378 as the BASE address of their parallel port. Other users would need to edit and re-code the member function to suit whatever BASE address their PC’s parallel port uses. Additionally, this solution does not use the data member BaseAddress.

5 OBJECT-ORIENTED PROGRAMMING 85

Class Definition – Improvement I

We need to provide the user with the ability to specify the base address according to hardware requirements. This is best done at the time of creating the ParallelPort object – ie at the time of calling the constructor. Our default constructor will need to be replaced with a constructor that allows us to set the member data BaseAddress to a desired value at the time of creating the object.

New constructor: We pass the parameter baseaddress to the constructor, which then assigns this value to the member data BaseAddress. Now the original outportb function will work as intended. You will have noticed in Listing 5-4 that the member function is defined separately from the class definition. The reason for this is explained in the box presented previously titled “In-line member functions”.

Listing 5-3 The class definition for ParallelPort - with a constructor.

class ParallelPort

{

private:

unsigned int BaseAddress;

public:

ParallelPort(int baseaddress); // constructor void WritePort0(unsigned char data);

};

Listing 5-4 Definitions of member functions for the improved ParallelPort class.

ParallelPort::ParallelPort(int baseaddress) // constructor

{

BaseAddress = baseaddress;

}

void ParallelPort::WritePort0(unsigned char data)

{

outportb(BaseAddress,data);

}

86 5 OBJECT-ORIENTED PROGRAMMING

NOTE

The member data BaseAddress is different to baseaddress. The

parameter baseaddress is a placeholder for the actual argument one would pass at the time of calling the constructor and plays a role only at the time of calling the constructor. On the contrary, the member data BaseAddress will

reside in memory storing data for the entire life of the object; in most cases, for the

whole life of the program.

Class Definition – Improvement II

Because most users will operate their parallel port at address 0x378, ideally we would like to have a constructor that by default sets the BASE address to 0x378. We still need the option of being able to set the port to other address values as was implemented in the previous class definition.

We achieve this by adding a second constructor (overloaded constructors) that sets the member data BaseAddress to 0x378 by default. This programmer-defined default constructor takes no parameters (ie. a default constructor) but assigns the member data BaseAddress to 0x378 inside its body. The class definition and member function definitions are given in Listing 5-5 and Listing 5-6.

Listing 5-5 Class definition for ParallelPort with a default constructor.

class ParallelPort

{

private:

unsigned int BaseAddress;

public:

ParallelPort(); // default constructor

ParallelPort(int baseaddress); // constructor void WritePort0(unsigned char data);

};

5 OBJECT-ORIENTED PROGRAMMING 87

Listing 5-6 Member function definitions for the class definition in Listing 5-5.

ParallelPort::ParallelPort() // default constructor

{

BaseAddress = 0x378;

}

ParallelPort::ParallelPort(int baseaddress) // constructor

{

BaseAddress = baseaddress;

}

void ParallelPort::WritePort0(unsigned char data)

{

outportb(BaseAddress,data);

}

If in our program we do not pass a value to the constructor, the default constructor will be called and the BASE address will default to 0x378. Alternatively we can set the BASE address to say 0x3BC by passing this value when creating a ParallelPort object as shown:

ParallelPort(0x3BC);

In this case the second constructor (takes a parameter) will be called to create the ParallelPort object. Although constructors commonly initialise some or all of the member data, a constructor can be programmed to carry out any actions a regular function may perform.

An important point to note is that the member function WritePort0() has not been specifically informed of the member variable BaseAddress – that is, BaseAddress has not been declared within this function. This is not necessary since BaseAddress and WritePort0() are both members of the same class, hence WritePort0() has unrestricted access to BaseAddress as explained in Section 5.3.3.

5.5 Using Class Objects in Programs

We will now use the ParallelPort object type developed in the previous section to output a byte of data to the port at BASE address. The object-oriented program that performs this task seems lengthy, however, the advantages of objectoriented programming will be realised as we progress through later chapters.

88 5 OBJECT-ORIENTED PROGRAMMING

Listing 5-7 Writing to port at address BASE using object-oriented approach.

/***************************************************** WRITING TO A PORT (object-oriented approach)

The program uses the fundamental ParallelPort object class to output a byte of data to the interface board.

*****************************************************/

#include <dos.h>

class ParallelPort

{

private:

unsigned int BaseAddress;

public:

ParallelPort(); ParallelPort(int baseaddress);

void WritePort0(unsigned char data);

};

ParallelPort::ParallelPort() // default constructor

{

BaseAddress = 0x378;

}

ParallelPort::ParallelPort(int baseaddress) // constructor

{

BaseAddress = baseaddress;

}

void ParallelPort::WritePort0(unsigned char data)

{

outportb(BaseAddress,data);

}

void main()

{

ParallelPort OurPort; // object instantiation

OurPort.WritePort0(255); // calling a member function

}

The class definition and function definitions are the same as developed earlier and have already been explained. What has not been explained is the body of the

5 OBJECT-ORIENTED PROGRAMMING 89

main() function. This is where a programmer uses the object classes to develop an application. The first of the two lines in main() creates an object named OurPort of type ParallelPort. The second line calls OurPort’s member function WritePort0()and outputs the value 255 to the port. The actual port address used by WritePort0() is assigned when we create the OurPort object by calling one of the two constructors. This process will now be explained in more detail.

Object Creation (instantiation)

The first line in main() is:

ParallelPort OurPort;

This is a declaration line and is similar to:

int a; // Data type is int, variable name is a.

Similarly; ParallelPort is the data type, which is an object type. OurPort is the name of the variable. The only difference is that int is a fundamental built-in data type whereas ParallelPort is a data type we developed.

In C++, the declaration lines not only inform the compiler the name (such as OurPort) and type of the object (such as ParallelPort), but also call the constructor of that object class to create the object. The creation of the object OurPort is what we mentioned earlier as instantiation – it brings into life a real usable object that will reside in memory. It is important to know the difference between ‘an object’ (also known as a class object) and ‘an object type’ (also known as the object class). The object is OurPort and the object type is ParallelPort. Thus, we have created an object named OurPort of object type ParallelPort.

Referring to the class definition given in Listing 5-5, we see that there are two constructor functions available. They are:

ParallelPort();

ParallelPort(int baseaddress);

Which of these two constructors were used by the declaration line to instantiate the OurPort object? The compiler will automatically select the correct constructor based on the parameters passed (or the absence of them). Since we did not specify a value for the parameter baseaddress at the time of instantiating the OurPort object, the compiler will call the first of the above two constructors, being the default constructor.

On the other hand, the declaration line in the main() function could have been:

ParallelPort OurPort(0x3BC);

In this case, the compiler will not call the default constructor, since a value for a parameter has been specified. Instead, the other constructor (which accepts a parameter) will be called to instantiate the object OurPort:

90 5 OBJECT-ORIENTED PROGRAMMING

ParallelPort(int baseaddress);

In the example above, when creating a ParallelPort object, the parameter baseaddress is replaced by the actual argument 0x3BC. The value 0x3BC will then be assigned to the data member BaseAddress (see the body of the constructor in Listing 5-6).

Returning to the program in Listing 5-7, the created object named OurPort will have all the features given in the class definition. It has its own private data member named BaseAddress and three public member functions. Two of the functions are constructors with the name ParallelPort and the other function is named WritePort0.

Sending data to the Port

The object we created named OurPort, makes use of its member function WritePort0() to send the value 255 out the port. The syntax for accessing this member function uses a period placed after the object name followed by the member name:

OurPort.WritePort0(255);

The other members of the object are accessed in exactly the same manner, although their access attributes may restrict access. For example, despite the correct syntax, any function outside the class trying to execute the following instruction will fail:

OurPort.BaseAddress = 0; // Will not work!

This is because BaseAddress is a private data member of the class and functions outside of the class cannot change it (the main() function is not part of the class). This illegal attempt to access the member data BaseAddress will be detected during compilation and a compilation error will be reported. The next section will present additional examples of access attributes used in our program.

Note that the WritePort0() function takes in one parameter. This parameter has been replaced by the actual argument 255. In binary, the decimal value of 255 will be represented with all eight bits set to 1.

The operation of the program can be checked by connecting the PC to the interface board as shown in Table 3-1. Passing the parameter 255 to the WritePort0()function should generate eight lit LEDs. You can change this number to any value between 0 and 255 inclusive. If you re-run the program you should see the LEDs light up accordingly.

5.5.1 Examples using Access Attributes

In the preceding section, an illegal attempt was made to assign the value 0 to data member BaseAddress, as now shown again:

OurPort.BaseAddress = 0; // Will not work!

5 OBJECT-ORIENTED PROGRAMMING 91

In that program, BaseAddress was declared as a private data member, which prevented the main() function from gaining access to change its value.

Instead of declaring BaseAddress as a private member, it can be declared as a public member. This being the case, it can be accessed by any function in the program and the compilation error will no longer appear. Listing 5-8 shows how this is done. Note that if you do not have a second parallel port (LPT2:) in your computer, the address 0x3BC does not exist, and the program will not function. However, it will compile error-free. Also, ensure the data cable that connects with the parallel port is plugged into the D25 connector of the correct port.

Listing 5-8 Declaring BaseAddress as a public data member.

/***************************************************** Note that the member variable BaseAddress is now declared under the public access attribute. Therefore it can be changed from within the main function and there will be NO compilation errors.

If your computer DOES NOT have a second parallel port, attempting to use the port address 0x3BC will FAIL!

*****************************************************/

#include <dos.h>

class ParallelPort

{

public: // Private access attribute has been // changed to public.

unsigned int BaseAddress;

public:

ParallelPort(); ParallelPort(int baseaddress);

void WritePort0(unsigned char data);

};

ParallelPort::ParallelPort()

{

BaseAddress = 0x378;

}

ParallelPort::ParallelPort(int baseaddress)

{

BaseAddress = baseaddress;

}

92 5 OBJECT-ORIENTED PROGRAMMING

void ParallelPort::WritePort0(unsigned char data)

{

outportb(BaseAddress,data);

}

void main()

{

ParallelPort OurPort;

OurPort.BaseAddress = 0x3BC; // Will NOT cause a // compilation error

OurPort.WritePort0(255);

}

Declaring member data as public is considered poor programming practice. The main purpose of using object-oriented programming is to form encapsulated objects, that are to some extent protected against misuse. This will not be the case if member data are declared to be public.

If a member variable needs to be changed, then it can be changed through a member function specifically designed for that purpose. This concept is demonstrated by the ChangeAddress() function shown in Listing 5-9.

Listing 5-9 The acceptable way to change a data member of an object.

/***************************************************** WRITING TO A PORT (Adding a member function to change the private member data).

Note: attempting to use the port address 0x3BC will fail if your computer doesn’t have a second parallel port.

*****************************************************/

#include <dos.h>

class ParallelPort

{

private: // Access attribute has been changed // back to private.

unsigned int BaseAddress;

public:

ParallelPort(); ParallelPort(int baseaddress);