Файл: Programming Microcontrollers in C, 2-nd edit (Ted Van Sickle, 2001).pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 15.06.2025

Просмотров: 4047

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Functions 57

will yield

x = 7*(y) +(y);

which is the wrong answer. The problem can be easily corrected by wrapping parentheses around the whole macro as

#define times_two(x) ((x)+(x))

Remember, always place any argument of a macro within parenthe­ ses and always place the entire macro definition in parentheses.

Note that there is no semicolon at the end of the macro defini­ tion. There should not be. If a semicolon were placed at the end of a macro definition, extra semicolons would be entered into expres­ sions containing macros with unpredictable results.

The function prototype

double sqr( double );

notifies the compiler that the function returns a double and takes a double argument.

Inside of the main function, the for loop

for(i=1;i<11;i++)

{

c=sqr(i);

printf(“\t%d\t%f\t%f\n”,i,c,square(c));

}

is used to calculate the several results. The variable c is of the type double, and i is an int. The expression c=sqr(i) will be ac­ cepted by the compiler. This function returns a double which can be stored in c. The argument is an int, but the compiler recognizes that sqr requires a double argument and converts i to a double before it is sent to the function sqr().

The code in the function sqr() is a restatement of a square root operation that we saw earlier. In this case, the function processes floating-point numbers rather than the integers used before. Three double variables are needed. The variables x1 and x2 are the current and last values found in the Newton iteration. As the result converges to the correct value for the square root, several things happen. Vari­ ables x1 and x2 become equal. The square of x2, or x1 for that matter, becomes equal to x. The product of x1 and x2 becomes

58 Chapter 1 Introduction to C

equal to x. Any of these tests can be used to determine if the esti­ mate has been through enough iterations to be accurate.

The macro definition abs(x) is used to test for the end of the loop. You will note that the argument is evaluated three times for the expansion of the macro. If we place a lot of calculation within the argument of a macro definition, the expansion of the macro may cause the code to calculate the argument to be repeated several times. For this reason, the expression

c = x1 - x2;

is placed inside of the while loop, and the test to determine loop termination uses abs(c).

At the end of a function, a return statement will cause the value of the expression following the word return to be evaluated and returned to the calling function. If this expression is not of the type specified by the function prototype, it will be converted to the cor­ rect type prior to being returned to the calling function. The expression following the return statement can be enclosed in parentheses or not.

Another example will show the use of static external variables.

/* Read in a string from the keyboard and print it out in reverse order. */

#include <stdio.h>

#define null 0

/* some function prototypes */ void push(int);

int pull(void);

int main(void)

{

int c; push(null);

while((c=getchar())!=’\n’)

push(c);

printf(“\n”);

while((c=pull())!=null)


Functions 59

putchar(c);

printf(“\n”); return 0;

}

The following code is to be compiled in a separate file from the code above:

/* the stack function */ #define MAX 100

static int buffer[MAX]; static int sp;

void push(int x)

{

if(sp < MAX) buffer[sp++]=x;

else

{

printf(“stack overflow\n”); exit(1);

}

}

/* the unstacking function */

int pull(void)

{

if(sp > 0)

return buffer[—sp]; else

{

printf(“stack underflow\n”); exit(1);

}

}

A stack is a last in, first out (LIFO) structure. Therefore, a stack can be used to reverse the order of data sent to it. The above program uses a stack operation. The functions push and pull identified in the

60 Chapter 1 Introduction to C

function prototypes perform the stacking operations for the main program. In the main program, a null is pushed onto the stack to identify the end of the data as it is pulled off of the stack a character at a time. Data are read in a character at a time, and as each character is read in, it is pushed onto a stack. When a new line character is detected, the input phase is stopped, and the data written to the stack is pulled off and printed. When the null is detected, the data have all been pulled off the stack, and the program is ended.

In the function above, the function exit() is used. This func­ tion is similar to the return operation. Whenever a call to the function exit is executed, the argument is evaluated, any files open for write are flushed and closed, and the control of the computer is returned to the operating system. The evaluation of the argument is returned to the operating system. Whenever a return is encoun­ tered, the expression following the return call is evaluated and returned to the calling function. If control is in main() when the return is encountered, the evaluation of the expression is returned to the operating system. Also, from main() all files open for write are flushed and closed. The function exit() and return work the same in main(), but exit() exits a program and returns con­ trol to the operating system from anywhere in the program.

In a separate compilation, the stack functions are compiled. In that function, the macro definition MAX is defined as 100. Macro definitions can be used to define any character string that is needed in a program. They are not limited to defining pseudo functions. Two external variables are defined in this file: an array of MAX integers named buffer and an int called sp. These variables are declared to be static. As such, these variables can be accessed by any function in the file, but they are not available to any function outside of the file. The variable sp is used as an index into the array buffer. When a push is executed, a test to determine if sp is less than MAX is com­ pleted. If sp is less than MAX, the data are stored at the sp location in buffer and sp is then incremented. Otherwise, an error message indicates that a stack overflow has occurred and the program is ex­ ited.

The pull() function is the reverse of the push() operation. First a check is made to see if there are some data on the stack to be pulled off. If there are data, the stack pointer is decremented, and the


Recursion 61

content of the buffer at that location is returned to the calling pro­ gram. In the event that sp is 0 when the pull operation is executed, a stack underflow message is sent to the screen prior to exiting the program.

The advantage to our making the buffer and the stack pointer in the stack functions static can be easily seen. Suppose that these vari­ ables could be accessed from anywhere in the program. In that case, it would not be necessary for the programmer to call the functions push or pull to stack and unstack data. If several different pro­ grammers were using the same stack for different tasks in one large program, it would be possible for different programmers to access the stack as expected, or from their own tasks. Suppose a program­ mer made the mistake of pre-decrementing the stack pointer on stacking and post-incrementing the stack pointer on unstacking. The whole program would suddenly be in chaos. Therefore, masking these variables from the rest of the program can reduce serious potential debugging problems.

Recursion

A recursive routine is one that calls itself. The C language is supposed to produce recursive code. Compilers for large machines usually support recursion, but recursion is often one of the first casu­ alties on small microcontrollers. Automatic variables are created when a function is entered, and they are stored on the stack. Therefore, each time a function is called, a new stack frame is created, and variables in place from an earlier execution of the function are unal­ tered. Such a function is called re-entrant, and re-entrant functions are also recursive. An example of a simple recursive function is the factorial:

n! = n*(n-1)*(n-2)*....

*2*1

An interesting observation that can be made of factorial is that

n! = n*(n-1)!

or n factorial equals n times n-1 factorial. Also, the factorial of 0 is defined as 1. With these definitions, it is possible to write the follow­ ing recursive function to calculate the factorial of a number:

long factorial( int n)

62Chapter 1 Introduction to C

{

if(n==0) return 1;

else

return n*factorial(n-1);

}

This surprisingly simple function calculates the factorial. When­ ever you write a recursive routine, it is important to have means of getting out of the routine. In the above case, when the argument reaches zero, the function returns a result rather than calling itself again. At that time the routine will work itself back a level at a time until it reaches the initial factorial call, and the calculation will be done.

Recursion can create some elegant code in that the code is very simple—often too simple. There is a cost in the use of recursive code, and that is stack space. Each time a function call is made, the argu­ ment is placed on the stack and a subroutine call is executed. As a minimum, the return address is two bytes, and the value of the argu­ ment is also two bytes. Thus, at least four bytes of stack space are needed for each function call. That is no problem when the factorial of a small number is calculated. (The factorial of 13 is larger than can be held in a long, so only small numbers can be considered for a factorial.) However, if a recursive function is written that calls it­ self many times, it is possible to get into stack overflow problems.

Another interesting recursive routine is the function to calculate a Fibonacci number. A Fibonacci number sequence is described by the following function:

long fib(int n)

{

if(n==1) return 1;

else if(n==0) return 1;

else

return fib(n-1) + fib(n-2);

}

This sneaky function calls itself twice. Some interesting characteris­ tics of this function are left to the exercises that follow.


Summary 63

EXERCISES

1.Write a function to calculate the Fibonacci number for 10, 20, 30, and 40.

2.Devise a means for determining the number of times the fib func­ tion is called in the above program. What is this number for fib(20)?

3.A separate problem from the number of times the function is called is the number of times the function is called without exiting through the bottom of the function. This term is called the depth of the function. Determine the maximum depth of the fib() function in calculating fib(20).

4.Repeat problem 1, but rewrite the Fibonacci number function so that it does not employ recursion. How does the time to execute this version of the fib(30) compared to that above?

Summary

The basics of writing programs in C have been discussed in this chapter. Several important concepts have been skipped over in this presentation and will be covered in Chapter 2.

If you have not done so, it is recommended that you enter and compile each example shown. These programs will all compile and run under the MIX PowerC Compiler, the Cosmic compiler for the M68HC11, the M68HC16, and the M68300 series of chips. They also compile on the DIAB MCORE compiler. With the exception of the MIX PowerC compiler, all of the compilers listed are cross com­ pilers that run on a PC platform, but compile code for another computer.

The ANSI version of the language is the current standard, and none of the classical C constructs have been introduced in this text. It is not to the programmer’s advantage to use the classical version of the language, even though programs that conform to classical C will compile on an ANSI compliant compiler. Any version of a C++ com­ piler structured to compile C code will also compile ANSI C code. The DIAB compiler listed above is a C/C++ compiler.