What's in Chapter 10?
Function Definitions
Function Calls
Parameter Passing
Making our C programs "look like" C++
Stack frame created by ICC11 and ICC12
Animation of ICC12 function call
Finite State Machine using Function Pointers
Linked list interpreter
We have been using functions throughout this document, but have
put off formal presentation until now because of their immense
importance. The key to effective software development is the appropriate
division of a complex problem in modules. A module is a software
task that takes inputs, operates in a well-defined way to create
outputs. In C, functions are our way to create modules. A small
module may be a single function. A medium-sized module may consist
of a group of functions together with global data structures,
collected in a single file. A large module may include multiple
medium-sized modules. A hierarchical software system combines
these software modules in either a top-down or bottom-up fashion.
We can consider the following criteria when we decompose a software
system into modules:
2) We wish to minimize the coupling or interactions between modules;
3) We wish to group together I/O port accesses to similar devices;
4) We wish to minimize the size (maximize the number) of modules;
5) Modules should be able to be tested independently;
6) We should be able to replace/upgrade one module with effecting the others;
7) We would like to reuse modules in other situations.
Figure 10-1: A module has inputs and outputs
As a programmer we must take special case when dealing with global
variables and I/O ports. In order to reduce the complexity of
the software we will limit access to global variables and I/O
ports. It is essential to divide a large software task into smaller,
well-defined and easy to debug modules. For more information about
modular programming see Chapter 2 of the book Embedded Microcomputer Systems: Real Time Interfacing by Jonathan Valvano published by Brooks-Cole.
The term function in C is based on the concept of mathematical functions. In particular,
a mathematical function is a well-defined operation that translates
a set of input values into a set of output values. In C, a function
translates a set of input values into a single output value. We
will develop ways for our C functions to return multiple output
values and for a parameter to be both an input and an output parameter.
As a simple example consider the function that converts temperature
in degrees F into temperature in degrees C.
short FtoC(short TempF){
short TempC;
TempC=(5*(TempF-32))/9; // conversion
return TempC;}
When the function's name is written in an expression, together
with the values it needs, it represents the result that it produces.
In other words, an operand in an expression may be written as
a function name together with a set of values upon which the function
operates. The resulting value, as determined by the function,
replaces the function reference in the expression. For example,
in the expression
FtoC(T+2)+4; // T+2 degrees Fahrenheit plus 4 degrees Centigrade
the term FtoC(T+2) names the function FtoC and supplies the variable T and the constant 2 from which FtoC derives a value, which is then added to 4. The expression effectively becomes
((5*((T+2)-32))/9)+4;
Although FtoC(T+2)+4 returns the same result as ((5*((T+2)-32))/9)+4, they are not identical. As will we see later in this chapter,
the function call requires the parameter (T+2) to be passed on the stack and a subroutine call will be executed.
Similar to the approach with variables, C differentiates between
a function declaration and a function definition. A declaration
specifies the syntax (name and input/output parameters), whereas
a function definition specifies the actual program to be executed
when the function is called. Many C programmers refer to function
declaration as a prototype. Since the C compiler is essential
a one-pass process (not including the preprocessor), a function
must be declared (or defined) before it can be called. A function
declaration begins with the type (format) of the return parameter.
If there is no return parameter, then the type can be either specified
as void or left blank. Next comes the function name, followed by the
parameter list. In a function declaration we do not have to specify
names for the input parameters, just their types. If there are
no input parameters, then the type can be either specified as
void or left blank. The following examples illustrate that the function
declaration specifies the name of the function and the types of
the function parameters.
// declaration input output
void Ritual(void); // none none
char InChar(void); // none 8-bit
void OutChar(char); // 8-bit none
short InSDec(void); // none 16-bit
void OutSDec(short); // 16-bit none
char Max(char,char); // two 8-bit 8-bit
int EMax(int,int); // two 16-bit 16-bit
void OutString(char*); // pointer to 8-bit none
char *alloc(int); // 16-bit pointer to
8-bit
int Exec(void(*fnctPt)(void)); // function pointer 16-bit
Normally we place function declarations in the header file. We
should add comments that explain what the function does.
void InitSCI(void); // Initialize 38400 bits/sec
char InChar(void); // Reads in a character, gadfly
void OutChar(char); // Output a character, gadfly
char UpCase(char); // Converts lower case character to upper
case
void InString(char *, unsigned int); // Reads in a String of max
length
To illustrate some options when declaring functions, we give alternative
declarations of these same five functions:
InitSCI();
char InChar();
void OutChar(char letter);
char UpCase(char letter);
InString(char *pt, unsigned int MaxSize);
Sometimes we wish to call a function that will be defined in another
module. If we define a function as external, software in this
file can call the function (because the compiler knows everything
about the function except where it is), and the linker will resolve
the unknown address later when the object codes are linked.
extern void InitSCI(void);
extern char InChar(void);
extern void OutChar(char);
extern char UpCase(char);
extern void InString(char *, unsigned int);
One of the power features of C is to define pointers to functions.
A simple example follows:
int (*fp)(int); // pointer to a function with input and output
int fun1(int input){
return(input+1); // this adds 1
};
int fun2(int input){
return(input+2); // this adds 2
};
void Setp(void){ int data;
fp=&fun1; // fp points to fun1
data=(*fp)(5); // data=fun1(5);
fp=&fun2; // fp points to fun2
data=(*fp)(5); // data=fun2(5);
};
The declaration of fp looks a bit complicated because it has two sets of parentheses
and an asterisk. In fact, it declares fp to be a pointer to any function that returns integers. In other
words, the line int (*fp)(int); doesn't define the function. As in other declarations, the asterisk
identifies the following name as a pointer. Therefore, this declaration
reads "fp is a pointer to a function with a 16-bit signed input parameter
that returns a 16-bit signed output parameter." Using the term
object loosely, the asterisk may be read in its usual way as "object
at." Thus we could also read this declaration as "the object at
fp is a function with an int input that returns an int."
So why the first set of parentheses? By now you have noticed that
in C declarations follow the same syntax as references to the
declared objects. And, since the asterisk and parentheses (after
the name) are expression operators, an evaluation precedence is
associated with them. In C, parentheses following a name are associated
with the name before the preceding asterisk is applied to the
result. Therefore,
int *fp(int);
would be taken as
int *(fp(int));
saying that fp is a function returning a pointer to an integer, which is not
at all like the declaration in Listing 10-1.
The second way to declare a function is to fully describe it;
that is, to define it. Obviously every function must be defined somewhere. So if
we organize our source code in a bottom up fashion, we would place
the lowest level functions first, followed by the function that
calls these low level functions. It is possible to define large
project in C without ever using a standard declaration (function
prototype). On the other hand, most programmers like the top-down
approach illustrated in the following example. This example includes
three modules: the LCD interface, the COP functions, and some
Timer routines. Notice the function names are chosen to reflect
the module in which they are defined. If you are a C++ programmer,
consider the similarities between this C function call LCD_clear() and a C++ LCD class and a call to a member function LCD.clear(). The *.H files contain function declarations and the *.C files
contain the implementations.
#include "LCD12.H"
#include "COP12.H"
#include "Timer.H"
void main(void){ char letter; short n=0;
COP_Init();
LCD_Init();
Timer_Init()
LCD_String("This is a LCD");
Timer_MsWait(1000);
LCD_clear();
letter='a'-1;
while(1){
if (letter=='z')
letter='a';
else
letter++;
LCD_putchar(letter);
Timer_MsWait(250);
if(++n==16){
n=0;
LCD_clear();
}
}
}
C function definitions have the following form
CompoundStatement
};
Just like the function declaration, we begin the definition with
its type. The type specifies the function return parameter. If there is no return
parameter we can use void or leave it blank. Name is the name of the function. The parameter list is a list of zero or more names for the arguments that will be
received by the function when it is called. Both the type and
name of each input parameter is required. As we will see later,
ICC11 and ICC12 pass the first (left most) parameter in Reg D,
and the remaining parameters are passed on the stack. Then once
inside the function, ICC12 and ICC12 functions will push register
D on the stack, so after that all parameters are on the stack.
The output parameter is returned in register D. 8-bit output parameters
are promoted to 16-bits. Similarly, most input parameters are
also passed as 16-bit values, 8-bit characters are promoted to
16-bit integers and arrays and strings are passed as pointers.
The exception to this rule is 32-bit longs and 32-bit floats.
Although a character is passed as a word, we are free to declare
its formal argument as either character or word. If it is declared
as a character, only the low-order byte of the actual argument
will be referenced. If it is declared as an integer, then all
16 bits will be referenced.
It is generally more efficient to reference integers than characters
because there is no need for a machine instruction to set the
high-order byte. So it is common to see situations in which a
character is passed to a function which declares the argument
to be an integer. But there is one caveat here: not all C compilers
promote character arguments to integers when passing them to functions;
the result is an unpredictable value in the high-order byte of
the argument. This should be remembered as a portability issue.
Since there is no way in C to declare strings, we cannot declare
formal arguments as strings, but we can declare them as character
pointers or arrays. In fact, as we have seen, C does not recognize
strings, but arrays of characters. The string notation is merely
a shorthand way of writing a constant array of characters.
Furthermore, since an unsubscripted array name yields the array's
address and since arguments are passed by value, an array argument
is effectively a pointer to the array. It follows that, the formal
argument declarations arg[] and *arg are really equivalent. The compiler takes both as pointer declarations.
Array dimensions in argument declarations are ignored by the compiler
since the function has no control over the size of arrays whose
addresses are passed to it. It must either assume an array's size,
receive its size as another argument, or obtain it elsewhere.
The last, and most important, part of the function definition
above is CompoundStatement. This is where the action occurs. Since compound statements may
contain local declarations, simple statements, and other compound
statements, it follows that functions may implement algorithms
of any complexity and may be written in a structured style. Nesting
of compound statements is permitted without limit.
As an example of a function definition consider
int add3(int z1, int z2, int z3){ int y;
y=z1+z2+z3;
return(y);}
Here is a function named add3 which takes three input arguments.
A function is called by writing its name followed by a parenthesized
list of argument expressions. The general form is
Name (parameter list)
where Name is the name of the function to be called. The parameter list specifies the particular input parameters used in this call. Notice
that each input parameter is in fact an expression. It may be
as simple as a variable name or a constant, or it may be arbitrarily
complex, including perhaps other function calls. Whatever the
case, the resulting value is pushed onto the stack where it is
passed to the called function.
C programs evaluate arguments from left to right, pushing them
onto the stack in that order. As we will see later, the ICC11
and ICC12 compilers allocate the stack space for the parameters
at the start of the program that will make the function call.
Then the values are stored into the pre-allocated stack position
before it calls the function. On return, the return parameter
is located in Reg D. The input parameters are removed from the
stack at the end of the program.
When the called function receives control, it refers to the first
actual argument using the name of the first formal argument. The
second formal argument refers to the second actual argument, and
so on. In other words, actual and formal arguments are matched
by position in their respective lists. Extreme care must be taken
to ensure that these lists have the same number and type of arguments.
It was mentioned earlier, that function calls appear in expressions.
But, since expressions are legal statements, and since expressions
may consist of only a function call, it follows that a function
call may be written as a complete statement. Thus the statement
add3(--counter,time+5,3);
is legal. It calls add3(), passing it three arguments --counter, time+5, and 3. Since this call is not part of a larger expression, the value
that add3() returns will be ignored. As a better example, consider
y=add3(--counter,time+5,3);
which is also an expression. It calls add3() with the same arguments as before but this time it assigns the
returned value to y. It is a mistake to use an assignment statement like the above
with a function that does not return an output parameter.
The ability to pass one function a pointer to another function
is a very powerful feature of the C language. It enables a function
to call any of several other functions with the caller determining
which subordinate function is to be called.
int fun1(int input){
return(input+1); // this adds 1
};
int fun2(int input){
return(input+2); // this adds 2
};
int execute(int (*fp)(int)){ int data;
data=(*fp)(5); // data=fun1(5);
return (data);
};
void main(void){ int result;
result=execute(&fun1); // result=fun1(5);
result=execute(&fun2); // result=fun2(5);
};
Notice that fp is declared to be a function pointer. Also, notice that the designated
function is called by writing an expression of the same form as
the declaration.
Now let us take a closer look at the matter of argument passing.
With respect to the method by which arguments are passed, two
types of subroutine calls are used in programming languages--call by reference and call by value.
The call by reference method passes arguments in such a way that references to the
formal arguments become, in effect, references to the actual arguments.
In other words, references (pointers) to the actual arguments
are passed, instead of copies of the actual arguments themselves.
In this scheme, assignment statements have implied side effects
on the actual arguments; that is, variables passed to a function
are affected by changes to the formal arguments. Sometimes side
effects are beneficial, and some times they are not. Since C supports
only one formal output parameter, we can implement additional
output parameters using call by reference. In this way the function
can return parameters back using the reference. As an example
recall the fifo queue program shown earlier in Listing 8-7. The function GetFifo, shown below, returns two parameters. The regular formal parameter
is a boolean specifying whether or not the request was successful,
and the actual data removed from the queue is returned via the
call by reference. The calling program InChar passes the address of its local variable data. The assignment
statement *datapt=Fifo[GetI++]; within GetFifo will store the return parameter into a local variable of InChar. Normally GetFifo does not have the scope to access local variables of InChar, but in this case InChar explicitly granted that right by passing a pointer to GetFifo.
int GetFifo (char *datapt) {
if (Size == 0 )
return(0); /* Empty if Size=0 */
else{
asm(" sei"); /* make atomic, entering critical section */
*datapt=Fifo[GetI++]; Size--;
if (GetI == FifoSize) GetI = 0;
asm(" cli"); /* end critical section */
return(-1); }
}
char InChar(void){ char data;
while(GetFifo(&data)){};
return (data);}
When we use the call by value scheme, the values, not references, are passed to functions.
With call by value copies are made of the parameters. Within a
called function, references to formal arguments see copied values
on the stack, instead of the original objects from which they
were taken. At the time when the computer is executing within
PutFifo() of the example below, there will be three separate and
distinct copies of the 0x41 data (main, OutChar and PutFifo).
int PutFifo (char data) {
if (Size == FifoSize ) {
return(0);} /* Failed, fifo was full */
else{
Size++;
*(PutPt++)=data; /* put data into fifo */
if (PutPt == &Fifo[FifoSize]) PutPt = &Fifo[0]; /* Wrap
*/
return(-1); /* Successful */
}
}
void OutChar(char data){
while(PutFifo(data)){};
SC0CR2=0xAC;}
void main(void){ char data=0x41;
OutChar(data);}
The most important point to remember about passing arguments by
value in C is that there is no connection between an actual argument
and its source. Changes to the arguments made within a function,
have no affect what so ever on the objects that might have supplied
their values. They can be changed with abandon and their sources
will not be affected in any way. This removes a burden of concern
from the programmer since he may use arguments as local variables
without side effects. It also avoids the need to define temporary
variables just to prevent side effects.
It is precisely because C uses call by value that we can pass
expressions, not just variables, as arguments. The value of an
expression can be copied, but it cannot be referenced since it
has no existence in global memory. Therefore, call by value adds
important generality to the language.
Although the C language uses the call by value technique, it is
still possible to write functions that have side effects; but
it must be done deliberately. This is possible because of C's
ability to handle expressions that yield addresses. And, since
any expression is a valid argument, addresses can be passed to
functions.
Since expressions may include assignment, increment, and decrement
operators (Chapter 9), it is possible for argument expressions to affect the values
of arguments lying to their right. (Recall that C evaluates argument
expressions from left to right.) Consider, for example,
func (y=x+1, 2*y);
where the first argument has the value x+1 and the second argument has the value 2*(x+1). What would be the value of the second argument if arguments
were evaluated from right to left? This kind of situation should
be avoided, since the C language does not guarantee the order
of argument evaluation. The safe way to write this is
y=x+1;
func (y, 2*y);
It is the programmer's responsibility to ensure that the parameters
passed match the formal arguments in the function's definition.
Some mistakes will be caught as syntax errors by the compiler,
but this mistake is a common and troublesome problem for all C
programmers.
Occasionally, the need arises to write functions that work with
a variable number of arguments. An example is printf() in the library. ICC11 and ICC12 implement this feature using
macros defined in the library file STDARG.C. To use these features
you include STDARG.H in your file. For examples see the STDIO.C
source file in your LIBSRC directory.
For every function definition, ICC11 and ICC12 generates an assembler
directive declaring the function's name to be public. This means that every C function is a potential entry point
and so can be accessed externally. One way to create private/public
functions is to control which functions have declarations. Consider
again the main program in Listing 10-2 shown earlier. Now lets
look inside the Timer.H and Timer.C files. To implement Private
and Public functions we place the function declarations of the
Public functions in the Timer.H file.
void Timer_Init(void);
void Timer_MsWait(unsigned int time);
The implementations of all functions are included in the Timer.C
file. The function, TimerWait, is private and can only be called by software inside the Timer.C
file. We can apply this same approach to private and public global
variables. Notice that in this case the global variable, TimerClock, is private and can not be accessed by software outside the Timer.C
file.
unsigned short TimerClock; // private global
void Timer_Init(void){ // public function
TSCR1 |=0x80; // TEN(enable)
TSCR2 = 0x01; // timer/2 (500ns)
TimerClock=2000; // 2000 counts per ms
}
void TimerWait(unsigned short time){ // private function
TC5 = TCNT+TimerClock; // 1.00ms wait
TFLG1 = 0x20; // clear C5F
while((TFLG1&0x20)==0){};}
void Timer_MsWait(unsigned short time){ // public function
for(;time>0;time--)
TimerWait(TimerClock); // 1.00ms wait
}
For more information about software development see Chapter 2
of the book Embedded Microcomputer Systems: Real Time Interfacing by Jonathan Valvano published by Brooks Cole.
Figure 10-2 illustrates the structure of a C stack frame. The
stack frame generated by the ICC11 compiler places the explicit
local variables (and other temporary data) at the top of the stack.
Input parameters to the function and the subroutine return address
are also on the stack. Recall that the 6811 stack pointer points
to an empty place just above the top element of the stack. Within
a 6811 function, RegX is not saved, but rather whenever stack access is required, the
SP is copied into RegX using the tsx instruction and the stack is accessed using X-index address.
The 6811 stack picture in Figure 10-2 illustrates the condition
after executing tsx. The stack frame generated by the ICC12 compiler is similar,
but not identical. Just like the 6811, the 6812 stack includes
local variables, temporaries, subroutine return address and the
input parameters. Just like the 6811, the first input parameter
is above the return address and the remaining input parameters
are below the return addressing. In actuality, this first input
parameter is passed into the function in RegD, and the function itself pushes it on the stack. Different from
the 6811, the 6812 stack pointer points to the top element of
the stack. Within the 6812 function, RegX is saved, and then RegX is using within the function as a stack frame pointer. Because
the value of RegX is maintained throughout the function and the 6812 stack is accessed
simply using X-index addressing without having to execute tsx or (tfr s,x) each time. In particular notice tsx is executed twice in the 6811 main program in Listing 10-10,
but tfr s,x is executed only once in the 6812 main program in Listing 10-11.
Figure 10-2: Stack frame for a function with one local variable
and three input parameters.
In order to understand both the machine architecture and the C
compiler, we can look at the assembly code generated. This example
shows a simple C program with three global variables x1,x2,x3, two local variables both called y and three function parameters z1,z2,z3.
int x1;
static int x2;
const int x3=1000;
int add3(int z1, int z2, int z3){ int y;
y=z1+z2+z3;
return(y);}
void main(void){ int y;
x1=1000;
x2=1000;
y=add3(x1,x2,x3);
The first compiler we will study is the ImageCraft ICC11 version
4.0 for the Freescale 6811. The disassembled output has been edited
to clarify its operation. The linker/loader allocates 3 segmented
memory areas: code pointed to by the PC; global accessed with
absolute addressing; and locals pointed to by the stack pointer
SP. The global symbols, _x1 _x2_x3, will be assigned or bound by the linker/loader. The pshx instruction allocates the local variable, and the tsx instruction establishes a stack frame pointer, X. This compiler
passes the first input parameter (z1) into the subroutine by placing it in register D. The remaining
parameters (z2, z3 in this example) are pushed on the stack by the main program
before the subroutine is called. The first operation the subroutine
performs is to push the remaining parameter on the stack (pshb psha) so that all three parameters, z1 z2 z3, are on the stack.
.area text ;text area is ROM
.globl _x3
_x3: .word 1000
.area text
.globl _add3
; y -> 0,x
; z3 -> 8,x
; z2 -> 6,x
; z1 -> 2,x
_add3: pshb ;push z1 on stack
psha
pshx ;allocate y
tsx ;create local stack frame
ldd 2,x ;RegD=z1
addd 6,x ;RegD=z1+z2
addd 8,x ;RegD=z1+z2+z3
std 0,x ;y=z1+z2+z3
pulx ;deallocate y
pulx ;deallocate z1
rts
.globl _main
; y -> 4,x
_main:
pshx ;allocate y
pshx ;allocate temporary (z3)
pshx ;allocate temporary (z2)
tsx
ldd #1000
std _x1 ;x1=1000
std _x2 ;x2=1000
ldd _x3
std 2,x ;z3=x3
ldd _x2
std 0,x ;z2=x2
ldd _x1 ;RegD=x1
jsr _add3
tsx
std 4,x ;y=x1+x2+x3
pulx ;deallocate temporary (z2)
pulx ;deallocate temporary (z3)
pulx ;deallocate y
rts
.area bss ;RAM
_x2: .blkb 2
.globl _x1
_x1: .blkb 2
The stack frame at the time of the addd instructions is shown in Figure 10-3. Within the subroutine the
local variables of main are not accessible.
Figure 10-3: ICC11 stack within the add3 function
The second compiler we will study is the ImageCraft ICC12 version
5.1 for the Freescale 6812. This disassembled output also has been
edited to clarify its operation. The linker/loader allocates 3
segmented memory areas: code pointed to by the PC; global accessed
with absolute addressing; and locals pointed to by the stack pointer
SP. The global symbols, _x1 _x2_x3, will be assigned or bound by the linker/loader. The three instructions
pshx tfr s,x and leas -8,sp allocates the local variable, and establishes a stack frame pointer,
X. This compiler passes the first input parameter (z1) into the subroutine by placing it in register D. The remaining
parameters (z2, z3 in this example) are pushed on the stack by the main program
before the subroutine is called. The first operation the subroutine
performs is to push the remaining parameter on the stack (pshd) so that all three parameters, z1 z2 z3, are on the stack. Also notice that the main program allocates space for the parameters it needs to push when
it calls add3 at the beginning of main.
Figure 10-4: ICC12 stack within the add3 function
Listing 10-11: ICC12 assembly of function call with local variables
.area text
_x3:: .word 1000
.area text
; y -> -2,x
; z3 -> 8,x
; z2 -> 6,x
; z1 -> 2,x
_add3:: pshd
pshx
tfr s,x
leas -2,sp
ldd 2,x
addd 6,x
addd 8,x
std -2,x
ldd -2,x
tfr x,s
pulx
leas 2,sp
rts
; y -> -2,x
_main:: pshx
tfr s,x
leas -8,sp
movw #1000,_x1
movw #1000,_x2
movw _x3,2,sp
movw _x2,0,sp
ldd _x1
jsr _add3
std -4,x
tfr d,y
sty -2,x
tfr x,s
pulx
rts
.area bss
_x2: .blkb 2
_x1:: .blkb 2
Figure 10-5: ICC12 animation of the add3 function
Now that we have learned how to declare, initialize and access
function pointers, we can create very flexible finite state machines.
In the finite state machine presented in Listing 9-2 and Listing 9-4, the output was a simple number that is written to the output
port. In this next example, we will actually implement the exact
same machine, but in a way that supports much more flexibility
in the operations that each state performs. In fact we will define
a general C function to be executed at each state. In this implementation
the functions perform the same output as the previous machine.
Figure 10-6: Finite State Machine (same as Figure 9-1)
Compare the following implementation to Listing 9-2, and see that the unsigned char Out; constant is replaced with a void (*CmdPt)(void); function pointer. The three general function DoStop() DoTurn() and DoBend() are also added.
const struct State{
void
(*CmdPt)(void);
/* function to execute */
unsigned short Wait; /* Time (E cycles) to wait */
unsigned char AndMask[4];
unsigned char EquMask[4];
const struct State *Next[4];}; /* Next states */
typedef const struct State StateType;
typedef StateType * StatePtr;
#define stop &fsm[0]
#define turn &fsm[1]
#define bend &fsm[2]
void DoStop(void){
PORTA
= 0x34;}
void DoTurn(void){
PORTA
= 0xB3;}
void DoBend(void){
PORTA
= 0x75;}
StateType fsm[3]={
{
&DoStop, 2000, // stop 1 ms
{0xFF, 0xF0, 0x27, 0x00},
{0x51, 0xA0, 0x07, 0x00},
{turn, stop, turn, bend}},
{
&DoTurn,5000, // turn 2.5 ms
{0x80, 0xF0, 0x00, 0x00},
{0x00, 0x90, 0x00, 0x00},
{bend, stop, turn, turn}},
{
&DoBend,4000, // bend 2 ms
{0xFF, 0x0F, 0x01, 0x00},
{0x12, 0x05, 0x00, 0x00},
{stop, stop, turn, stop}}};
Compare the following implementation to Listing 9-4, and see that the PORTH=pt-Out; assignment is replaced with a (*Pt->CmdPt)(); function call. In this way, the appropriate function DoStop() DoTurn() or DoBend() will be called.
void control(void){ StatePtr Pt;
unsigned char Input; unsigned short startTime; unsigned int i;
TSCR1 |= 0x80;
// TEN(enable)
TSCR2 = 0x01;
// timer/2 (500ns)
DDRA = 0xFF; // PortA bits 7-0 are outputs
DDRB = 0x00; // PortB bits 7-0 are inputs
Pt = stop; // Initial State
while(1){
(*Pt->CmdPt)();
//
1)
execute
function
startTime = TCNT; // Time (500 ns each) to wait
while((TCNT-startTime)<Pt->Wait); // 2) wait
Input = PORTB; // 3) input
for(i=0;i<4;i++)
if((Input&Pt->AndMask[i])==Pt->EquMask[i]){
Pt=Pt->Next[i]; // 4) next depends on input
i=4; }}};
In the next example, function pointers are stored in a listed-list.
An interpreter accepts ASCII input from a keyboard and scans the
list for a match. In this implementation, each node in the linked
list has a function to be executed when the operator types the
corresponding letter. The linked list LL has three nodes. Each node has a letter, a function and a link
to the next node.
// Linked List Interpreter
const struct Node{
unsigned char Letter;
void (*fnctPt)(void);
const struct Node *Next;};
typedef const struct Node NodeType;
typedef NodeType * NodePtr;
void CommandA(void){
OutString("\nExecuting Command a");
}
void CommandB(void){
OutString("\nExecuting Command b");
}
void CommandC(void){
OutString("\nExecuting Command c");
}
NodeType LL[3]={
{ 'a', &CommandA, &LL[1]},
{ 'b', &CommandB, &LL[2]},
{ 'c', &CommandC, 0 }};
void main(void){ NodePtr Pt; char string[40];
InitSCI(); // Enable SCI port
TSCR |=0x80; // TEN(enable)
TMSK2=0xA2; // TOI arm, TPU(pullup) timer/4 (500ns)
OutString("\nEnter a single letter command followed by <enter>");
while(1){
OutString("\n>");
InString(string,39); // first character is interpreted
Pt=&LL[0]; // first node to check
while(Pt){
if(string[0]==Pt->Letter){
Pt->fnctPt(); // execute function
break;} // leave while loop
else{
Pt=Pt->Next;
if(Pt==0) OutString(" Error");}}}}
Listing 10-14: Linked list implementation of an interpreter.
Compare the syntax of the function call, (*Pt->CmdPt)();, in Listing 10-13, with the syntax in this example, Pt->fnctPt();. In the
ImageCraft compilers, these two expressions both generate
code that executes the function.
No comments:
Post a Comment