Thursday, December 29, 2011

Embedded C Tutorial (Chapter 10: Functions)


What's in Chapter 10?
Function Declarations
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:
1) We wish to make the overall software system easy to understand;
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.

Function Declarations

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

Listing 10-1: Example of a function pointer
 
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.

Function Definitions

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

Listing 10-2: Modular approach to software development
 
C function definitions have the following form
type Name(parameter list){
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);}

Listing 10-3: Example function with 3 inputs and one output.
 
Here is a function named add3 which takes three input arguments.


Function Calls

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

Listing 10-4: Example of passing a function pointer
 
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. 

Argument Passing

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

Listing 10-5: Multiple output parameters can be implemented using call by reference
 
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);}

Listing 10-6: Call by value passes a copy of the 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.

Private versus Public Functions

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

Listing 10-7: Timer.H header file has public functions
 
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
}

Listing 10-8: Timer.C implementation file defines all functions
 
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.
 
The Stack Frame

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

Listing 10-9: Example function call with local variables
 
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

Listing 10-10: ICC11 assembly of function call with local variables
 
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
    .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

Listing 10-11: ICC12 assembly of function call with local variables
 
Figure 10-5: ICC12 animation of the add3 function
 
Finite State Machine using Function Pointers

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

Listing 10-12: Linked finite state machine structure stored in ROM

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

Listing 10-13: Finite state machine controller for MC68HC812A4
 
Linked List Interpreter using Function Pointers

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