📄 chapter3.htm
字号:
<head>
<title>Chapter 3 Some Useful Classes</title>
</head>
<body>
<h1>Some Useful Classes</h1>
<p>
One fact discourages many experienced programmers of microcontrollers when
they examine Object Oriented Programming. Most texts and articles on OOP are directed
toward data structures that are converted to objects. Linked lists, tree structures,
containers, even complex arithmetic are data structure items that have little use in the
world of the microcontroller. Unfortunately, OOP can have great impact on the design
and programming of these embedded controllers, and most programmers never allow
these advantages to be employed in their programs. In these cases, the class probably best
represents a peripheral or perhaps a control function. It should be the goal of the
programmer to provide a clean, reusable, easy to understand and maintain program that
accurately meets the need of the program specification. We must consider the good and
bad of OOP and other techniques when writing our code and then decide the
programming approach that best suites each individual application. In this chapter, we
will look at several potential classes that can be used with microcontrollers. Most of
these items will be used to either interface with on-board peripherals or provide exclusive
access to a peripheral from each of several tasks running in the machine.
<p> Several tasks? As we work through the remainder of this text, the concept of
multiple tasks will be explored and developed. To begin, let us simply admit that a single
controller can process several independent programs simultaneously or at least appear to
execute these various programs at the same time. There will be no attempt in this text to
develop a complete multitasking operating system. However, we shall use multiple tasks
that must work cooperatively to accomplish the goal of the program. Therefore, some
concepts that belong to multitasking operating systems will show up in our programs. As
an aside, if the program being developed requires many of the features found in a standard
multitasking operating system, it is probably the best choice to purchase one of the several
available operating systems to use on the machine. These programs are complicated,
large, and very specialized and they have been developed by specialists with vast
experience in the field. It makes little sense to redevelop all of this code when it has
already been done for you. To begin with though, let us look at some features that will be
needed by the basic system and see how these features can be used as classes from which
we can create useful objects.
<p> In the Chapter 2, we saw how a class can be derived that will support <a href="chapter2.htm#polymorphism"> inheritance
and virtual functions</a>. There is a cost to our making these features available to the class.
The earilier version that allowed several instantiations of each object had only the
attributes contained within the class structure. The more general class had to contain not
only all of the class attributes, but each object also contained pointers to the methods
pertaining to the class. When instances of objects are created, memory space for the
object location is taken from the program heap. The heap is an undedicated block of
RAM accessable by the program. In a case where there is an operating system, the
allocation of heap space is made by the operating system, not the program. Every
instantiation of an object requires RAM, and usually RAM is a limited resource on any
microcontroller. Therefore, when using objects, the programmer will usually attempt to
minimize the RAM usage.
<p> On the other hand, the objects that use the least RAM can provide neither
inheritance nor virtual functions. Another important feature is lost. Recall in the later
implementations of our class methods, it was possible to make the methods static, thus
limiting them to file scope. The method names could be used elsewhere in the programs
with no danger of multiple defined function names at link time. This latter problem can be
easily overcome by using method names that are unlikely to be found elsewhere in any
program. The class name should be a part of the name of any method attached to the
class.
<h2><a name="the_semaphore">The Semaphore</a></h2>
<p>
A class that is very useful is the semaphore. A semaphore is a flag, or token, that
can be made a part of any resource. When a process or a task needs exclusive access to a
resource, it must attach the resource semaphore before any access to the resource is
permitted. The semaphore is then assigned to the resource. As long as the resource is
attached to the initial process, no other process can access the resource. When finished
with the resource, the semaphore is released and another process can use the resource.
<p> Listed below is a header file for a semaphore. The class structure contains only
one member, namely sem. There are four methods. Each method has the word
semaphore in its name. There should be little danger of multiple definitions with these
names. At this time, there is no intent to use semaphore as a base class. Therefore, the
use of global functions for the semaphore methods is indicated. This approach will use the
least memory space for each instance of a semaphore. The function calls will all be direct
to the functions rather than through pointers contained in the object. Therefore, the actual
code will be simpler and smaller than would be obtained if we had used the more
complicated form of the class.
<pre><code>
#ifndef SEMAPHORE_H
#define SEMAPHORE_H
#include "defines.h"
typedef struct semaphor
{
Boolean sem;
}Semaphore;
Boolean attach_semaphore(Semaphore *);
void release_semaphore(struct semaphor *);
Boolean semaphore_status(struct semaphor*);
void wait_for_semaphore(struct semaphor*);
Semaphore* semaphore_(void);
void semaphore__(Semaphore *);
#endif
</pre></code>
<p> The implementation file is shown below. The first method listed is
attach_semaphore( ). There are a couple of pieces of business that must be taken care of
in this method that has no counterpart in the language C or C++. Therefore, we will use
assembly language inserts in the C program. The first line of code is actually two
assembly instructions. The first pushes the content of the condition code register on the
stack for later access. It then performs the logical OR of the condition code register
contents with a value 0x00e0. Those familiar with the MC68HC16 will recognize that this
operation has disabled the interrupts of the microcontroller. This approach avoids a
potential problem that could allow two processes attached to the same semaphore in error.
<pre><code>
#include "semaphor.h"
Boolean attach_semaphore(Semaphore *s)
{
_asm("pshm ccr\norp #00e0H\n"); /*disable the interrupts */
if(s->sem==FALSE)
{
s->sem=TRUE;
_asm("pulm ccr\n"); /* enanble interrupts */
return TRUE;
}
else
{
_asm("pulm ccr\n"); /* enanble interrupts */
return FALSE;
}
}
void release_semaphore(Semaphore *s)
{
s->sem=FALSE;
}
Boolean semaphore_status(Semaphore *s)
{
return s->sem;
}
void wait_for_semaphore(Semaphore *s)
{
while(semaphore_status(s));
attach_semaphore(s);
}
Semaphore *semaphore_(void)
{
Semaphore *this
if(!(this=(Semaphore*)malloc(sizeof(Semaphore))))
error_handler();
this->sem=FALSE; /* default is unattached */
return this;
}
void semaphore__(Semaphore *this)
{
free( this);
}
</pre></code>
<p> After the interrupts are disabled, the content of s->sem is examined, and if it is
FALSE it is set to TRUE. It is at this point in the code that a problem could have
occured. Suppose that the program is executing the line of code
<pre><code>
if(s->sem==FALSE)
</pre></code>
<p>
The compiled version of this code is shown below. The two instructions on lines 18 and
19 load the contents of s->sem into the d register and the conditional branch is then taken
depending on the value of s->sem . Assume that it is FALSE. Suppose that during the
execution of the code in line 19, an interrupt would occur. Further suppose that as a
result of that interrupt, another process is launched and it needs the use of the resource
protected by the semaphore being attached in the code being examined. The second
process would access s->sem and find it to be FALSE because the first process had not
yet marked the semaphore as TRUE. The second process would then claim the
semaphore and the resource as its own and proceed. As time passes and program
executes, the first process would eventually regain control of the program and would start
execution where it left off. At that point, process 1 would discover that s->sem is
FALSE, the value saved just prior to the interrupt, and would claim the semaphore as its
own and proceed forward to disaster because two process would each have exclusive
access to the same resource.
<pre><code>
17 ; 6 if(s->sem==FALSE)
18 000A CD02 ldy OFST+2,x
19 000C 9500 ldd 0,y
20 000E B606 bne L1
</pre></code><p>
The line of code that disables all of the system interrupts will prevent the
simultaneous access of more than one process to an exclusive resource. The interrupts are
disables while lines 18 and 19 above are executed so, the problem described above cannot
happen. Immediately following the test and set of the semaphore, the interrupts are
enabled so that normal processing can continue.
<p> The remainder of the semaphore methods are straight forward. The semaphore is
available whenever s->sem is FALSE. release_semaphore( ) simply sets s->sem to
FALSE so that the semaphore can be made available to another process. The method
semaphore_status( ) returns the Boolean value of s->sem. Finally wait_for_semaphore( )
holds the calling process in the wait_for_semaphore( ) method until the semaphore
becomes available. When the semaphore becomes available, it is immediately given to the
waiting process and control is returned to that process.
<p> The <a href="chapter1.htm#constructor">constructor</a> allocates memory space for the semaphore and sets its value to
FALSE. The destructor destroys the semaphore.
<p> There are other types of semaphores called counting sempahores which can be
readily implemented by the above means. We will use the above simple semaphore in
several instances later in this text. The semaphore will be used whenever it is necessary to
grant exclusive access of a resource to one of several competing processes, each of which,
requires the resource.
<h2><a name="linked_list">Linked List</a></h2>
<p>
Many of the object oriented programming texts stress creation of objects that are
in reality data structures. One seen often is the code associated with a list of some sort.
Lists, queues, stacks, singly linked list, doubly linked list, circular lists, etc. are all
derivatives of common list operations. These lists are easily implemented as classes and
lend themselves to object oriented programming.
<p> We will start with a very practical list operation. This class will be a doubly linked
list, and it will make use of some advanced programming techniques to simplify the code
being generated. Such a list will contain three separate classes. The first will be a class
that when instantiated will be an object to be stored in the list. The first class is called an
object. The second class implements a simple node that contains a pointer to the object
being stored, and two pointers needed to implement the links. This class is named <a href="chapter3.htm#link_class">link</a>.
Finally the <a href="chapter3.htm#linklist_class">linklist class</a> contains the collections of links and the code necessary to add,
delete, and exercise the objects linked together in the linked list.
<h3><a name="object_class">Object Class</a></h3>
<p> It is important that flexibility be maintained when creating different types of
classes. It is possible, for example, that one could specify the type of object that is to be
stored in our linked list in some detail. Such an approach would lock the linked list into
the use of the specified object only, and modifications of the content of the list would
force serious reprogramming. Such an approach is quite inflexible and should be avoided
whenever possible.
<p> The single most effective mechanism for maintaining flexibility in the program is to
use pointers to objects that are being used in all outside objects rather than the objects
themselves. A pointer is simply a single entry in an object while an object could be many
bytes of code. Therefore, additional instantiations of the object will be smaller because the
object need not contain a reproduction of the object but rather a pointer to it is sufficient.
You will find pointers used extensively in the following sections.
<p> The base object is a thing to be put into the list. That object can be anything, and
we will see instances later where the base object can be a delay in time or perhaps a
representation of a task that must execute sometime. It is not necessary that the objects
stored on the list be of the same type. When all of the objects are of the same type, there
will probably be some common operation that each object will require. These common
operations should be methods for the base object. Any attempt to associate the common
operations with the link or the link list objects complicates the code and makes it
extremely difficult to sort out any errors.
<p> For this example, we are going to create a link list of objects that merely create a
delay. At the end of the specified delay, the computer bell operation will be executed.
Therefore, you can hear how the program is progressing. Such an object needs one
attribute, the amount of time for the delay, and one method that will cause the delay to be
executed. The following header file defines such an object. The attribute is named count,
and the method is named exercise. Note that the method is defined in the structure
content definition statement as a pointer to a function.
<pre><code>
#ifndef OBJECT_H
#define OBJECT_H
#include "defines.h"
#define OBJECT \
void (*exercise)(struct object*); \
long count;
typedef struct object
{
OBJECT
} Object;
Object *object_(long count);
void object__(Object *);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -