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

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

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

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

Добавлен: 15.06.2025

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

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

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

48 Chapter 1 Introduction to C

has the normal printf arguments. Note however that the string is not confined to one line. C compilers will not allow a string to be split among several lines in a program. However, an ANSI C compli­ ant compiler will cause two adjacent strings to be concatenated into a single string, so the above code will compile without error.

Break, continue, and goto

These commands will cause a C program to alter its program flow. If a break statement is encountered, the program will exit the loop in which it is executing. Break can be used to exit for, while, do-while, and switch statements. An example of the use of the break statement is shown in the next section.

The continue statement causes the next iteration of a for, while, or a do while loop to be started. In the case of the for statement, the last argument is executed, and control is passed to the beginning of the for loop. For both the while and the do-while, the argument of the while statement is tested immediately, and the program proceeds according to the result of the test.

The break statement is seen frequently, and the continue statement is rarely used. Another statement that is even more rarely used is the goto. In C, the programmer can create a label at any location by typing the label name followed by a colon. If it is neces­ sary, the goto <label> can be used to transfer control of the program from one location to another. In general, C provides enough structured language forms that the use of the goto <label> se­ quence will rarely be needed. One place where the goto can be used effectively is when the program is nested deeply and an error is detected. In such a case, the goto statement is an effective means of unwinding the program from a deep loop to an outer loop to process the error. In general, you should avoid goto statements whenever possible.

That said, there is an excellent alternative to a goto. The reach of a goto is limited to the function in which it is defined. In fact, the reach should probably be confined to the block in which it is de­ fined. Since new variables can be defined at the beginning of any block, undefined behavior can be introduced when a goto branches into a block where new variables have been defined. Also, you can introduce undefined behavior when you branch out of a block where

Program Flow and Control

49

variables have been defined. In both cases, the branch is around op­ erations of defining or deleting local variables. The alternative is to make use of the setjmp() and longjmp() functions.

These handy library functions are declared in the setjmp.h header file. Also declared in this header is a type called env. In your program, you must define an external instance of env. This param­ eter is used as an argument to setjmp(). setjmp() saves the status of the computer in the instance of env when it is called. When called originally setjmp()returns a zero or a FALSE. The func­ tion longjmp() takes two arguments. The first is the env variable corresponding to the return location in the program. The second is an integer that is returned. Executation of the longjmp() returns control to within the setjmp() function. When control is returned to the function that called the setjmp() the parameter that was passed from the longjmp() is returned. Therefore, when control returns from setjmp() a simple test determines whether the func­ tion setjmp() or longjmp() was called.

These functions restore the status of the computer to that which existed when the setjmp() was called. This restoration automati­ cally unrolls all function calls between the execution of the setjmp() and the longjmp(). Also, there are no block or func­ tion limits on the use of these functions. As long as env is a globally accessible variable, longjmp() can pass control from any location in a program to any other.

The Switch Statement

A second approach to selection between several alternates is the switch statement. This approach is sometimes called the switch/case statement. Following is a program that accomplishes exactly the same as the above program. In this case, the switch statement is used.

/* Count the number of occurrences of each vowel found in an input and also count all other charac­ ters. */

#include <stdio.h>

int main(void)

{


50 Chapter 1 Introduction to C

int na=0,ne=0,ni=0,no=0,nu=0; int nother=0,c;

while ((c=getchar())!=EOF) switch( c)

{

case ‘A’:

case ‘a’: na=na+1; break;

case ‘E’:

case ‘e’: ne=ne+1; break;

case ‘I’:

case ‘i’: ni=ni+1; break;

case ‘O’:

case ‘o’: no=no+1; break;

case ‘U’:

case ‘u’: nu=nu+1; break;

default:

nother=nother+1;

}

printf(“As=%d, Es=%d, Is=%d, Os=%d, Us=%d and” “ Others=%d\n” ,na,ne,ni,no,nu,nother);

return 0;

}

This program performs exactly the same function as the earlier one. The data are read in a character at a time as before. Here, however, the switch statement is used. The statement switch(c) causes the argument of the switch to be compared with the constants follow­ ing each of the case statements that follows. When a match occurs, the next set of statements to follow a colon will be executed. Once the program starts to execute statements, all of the following state­ ments will be executed unless the programmer does something to cause the program to be redirected. The break instruction does ex­


Functions 51

actly this operation for us. When a C program encounters a break, it jumps to the end of the current block. Therefore, the breaks following the executable statements above will cause the program to jump out of the executing sequence and return to get the next charac­ ter from the input stream.

When all options have been exhausted without a match, the state­ ments following the default line will be executed. It is not necessary to have a default line.

EXERCISES

1.Write a program that counts the number of lines, words, and char­ acters in an input stream.

2.Extend the program from the exercise above to calculate the per­ centage usage of each character in the alphabet.

3.A prime number is a number that cannot be evenly divided by any number. For example, the numbers 1, 2, and 3 are all prime num­ bers. Write a program that will calculate and print out the first 200 prime numbers.

Write this program without the use of either a modulo or a divide operation.

Functions

The function is the heart of a C program. In fact, any C program is merely a function named main. The purpose of a function is to provide a mechanism to allow a single entry of a code sequence that is to be repeated many times. A function is the most reusable element in the C language. Properly written and debugged functions can be collected into a program when needed. Therefore, the use of func­ tions will allow the programmer to write smaller programs and it is not necessary to rewrite common functions that are used often.

A function can have many arguments or none whatsoever. Func­ tion arguments are contained in parentheses following the function name. The values of the arguments are the parameters needed to execute the function. A function can return a value, or perhaps it will not have a return value. An example of a function that returns a value is getchar() which returns a character from the input stream.

52 Chapter 1 Introduction to C

A function may not be nested inside another function. Therefore, any function must be created outside of the boundaries of any other function or program structure. Functions have only one entry point, and they return only one return item. The return can be of any type that C supports. Functions can have several arguments. The argu­ ments can be of any valid C type.

In ANSI C, the use of a function requires the use of a function prototype. A function prototype is a statement of the following form:

type function_name(type, type, type,...);

The first type preceding the function name is the type to be re­ turned from the function. It is also called the type of the function. The several types found in the argument are the types of the corre­ sponding arguments that are sent to the function. Variable names may or may not be used for the arguments of a function prototype. The type list is the important item. The types in the argument list are separated by commas.

Thus far, it might seem that we have been blindly using functions like printf(), getchar(), and putchar() without the ben­ efit of function prototypes. Not so! The header file stdio.h contains the function prototypes of all input/output related functions, so it is not necessary for you to put a function prototype in your code for these functions. Other library functions have their prototypes in their own header files.

Compilers will differ. If a programmer attempts to send the wrong type of data to a function through its argument, the compiler might consider it an error or it might well convert the argument to the cor­ rect type prior to calling the function. In either case, the compiler will not let a program use the wrong type of data as an argument to a function. The standard allows that the parameter type be corrected to the correct type and proceed. Some embedded systems compilers will require that the type of each parameter be correct before the program can compile.

One item that is important. Copies of parameters are passed to any function. Copies are placed on the system stack or in registers or both before the function call is executed. Therefore, the program can use these parameters in any way without altering the calling pro­ gram. In fact, after you have done what is needed with a parameter, you may use the parameter as a storage location for your function.


Functions 53

Data returned from a function will always be converted to the correct type before it is passed back to the program. If you wish to have a different type returned, the cast operator can be used to change the return data type to any type desired.

ANSI C defined the type void. This type is used in several dif­ ferent ways. If there is no function return, the prototype must identify the function as type void. Also when there are no function argu­ ments, the argument list must contain the type void. This use of the keyword void will prevent problems in function calls.

Note that the function prototype above is terminated with a semi­ colon. The semicolon is needed in the function prototype, but it is not to be used after the name of the function in the code where the function is defined. The function prototype is a declaration state­ ment that merely provides information to the compiler while the function prologue, the first line of the function, is a function defini­ tion which opens the code for the function.

The philosophy in C is to use functions with little provocation. Using many functions produces code that is easy to read and follow. Often it is easier to debug many small functions rather than a larger program. One must temper these ideas somewhat when writing code for small microcontrollers. Calling a function requires some over­ head that is repeated each time the function is accessed. If the total overhead is more than the length of the function, it is better to use in-line code. In-line code implies that the function code is repeated in-line every time that it is needed. If the function code is much greater than the calling overhead, the function should be used. In between these limits, it is difficult to determine a hard-and-fast rule. In microcontroller applications, it is probably best to use function calls to a single function if there is a net savings of memory as a result. This savings is calculated by first determining the code needed prior to calling the function, the code needed to clean up the process after the function call, the number of times the function is called, and the length of the function. The in-line code will be smaller than the corresponding function code. Therefore, if the total code for the num­ ber of function calls listed first exceeds the total in-line code required to accomplish the same operations, then use the in-line code. Other­ wise, use function calls.

54 Chapter 1 Introduction to C

The above argument is valid for microcontroller applications code. It does not necessarily follow for code written for large computers. When writing for a large computer, there are usually few memory constraints. In those cases, it is probably best to use more function calls and not be worried about the memory space taken up by func­ tion calls unless there is a serious speed constraint. When speed is a problem, the programmer must go through an analysis similar to that above with the dependent parameter being time rather than memory space. In small computers where several registers can be saved and restored when a function is entered and exited, single instructions can require many clock cycles. When deciding whether to use a func­ tion or in-line code, the programmer must assess the total time lost to entering and exiting a function each time it is entered, and weight that time lost as a fraction of the total time the program resides in the function. If this time is large, and the program requires too much execution time, consider the use of in-line functions.

It is always good to write small functions and create simple call­ ing programs to exercise the small functions. These programs are used to debug the functions, and they are discarded after the func­ tions are debugged. If later, the program constraints dictate that in-line code should be used, the essential code of the function can be written into the program wherever it is needed. Another approach that will be discussed in the next chapter is to use a macro definition to specify a small function. With a macro definition, the function code is writ­ ten in-line to the program whenever the function is invoked.

Let us revisit an example used earlier. Write a program to calcu­ late and display the square root of each integer less than 11:

/* Calculate and display the square roots of numbers

1 <= x <=10 */

#include <stdio.h>

#define abs(t) (((t)>=0) ? (t) : -(t)) #define square(t) (t)*(t)

double sqr ( double ); int main(void)


Functions 55

{

int i; double c;

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

{

c=sqr(i);

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

}

return 0;

}

/* the square root function */

double sqr( double x )

{

double x1=1,x2=1,c;

do

{

x1=x2;

x2=(x1 + x/x1)/2; c=x1-x2;

}

while(abs(c) >= .00000001); return x2;

}

The result of this calculation is shown below:

1

1.000000

1.000000

2

1.414214

2.000000

3

1.732051

3.000000

4

2.000000

4.000000

5

2.236068

5.000000

6

2.449490

6.000000

7

2.645751

7.000000

8

2.828427

8.000000

9

3.000000

9.000000

10

3.162278

10.000000

56 Chapter 1 Introduction to C

The second line of code

#define abs(t) (((t)>=0) ? (t) : -(t))

is called a macro definition. In this case, the macro definition has the appearance of a simple function. This function will calculate the ab­ solute value of the argument t. The absolute value of the argument is a positive value. If the argument is positive, it is returned unchanged. If it is negative, it is multiplied by –1 before it is returned. A macro definition is a type of character expansion. Whenever the function abs(x) is found in the code, the character string (((x)>=0) ?

(x) : -(x)) is put in its place. The argument x can be any valid C expression. This function returns the absolute value of its argu­ ment. The macro definition

#define square(t) (t)*(t)

returns the square of t. Since these arguments can be any valid C expression, it is necessary to be cautious when writing the macro defi­ nitions. Suppose that the parentheses were left out of the above expression, and the macro were written

#define square(t) t*t

Also suppose that the code using this function were as follows:

x=square(y+3);

The character expansion of this expression would be

x=y+3*y+3;

The result of this calculation is 4*y+3 and not (y+3)*(y+3) as expected. When writing macro definitions, surround all arguments and functions created by the macro with parentheses so that all argu­ ments are evaluated prior to use in the macro definition function.

Another problem can sneak into your code through improperly written macros. Suppose that you want a macro that doubles the value of its argument. Such a macro could be written

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

This macro when expanded in the following expression

x = 7*times_two(y);