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

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

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

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

Добавлен: 15.06.2025

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

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

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

84Chapter 2 Advanced C Topics

3.Write a function that will calculate the vector product of two 1 by 4 matrices. The vector product of two arrays is the sum of the products of the corresponding elements of the two arrays.

4.Write a function that will calculate the matrix product of two 4 by 4 matrices. The matrix product is a matrix whose i,j element is the vector product of the ith row of the first matrix and the jth column of the second matrix. As such, the product of two 4 by 4 matrices is a 4 by 4 matrix.

C has pointers to functions. A common use for pointers for func­ tions is in creating vector tables for microcontrollers. Most microcontroller applications involve the use of interrupts. When an interrupt occurs, the machine status is saved, and program control is transferred to an interrupt service routine. At the close of the inter­ rupt service routine, the machine status is returned to the earlier condition, and control is returned to the interrupted program. An address table in memory called the vector table contains the addresses of each interrupt service routine. It is the programmer’s responsibil­ ity to fill the vector table with the proper addresses for the various interrupt service routines. Pointers to functions allow the program­ mer access to the addresses of the interrupt service routines. We will see several different methods to create the vector tables in the chap­ ters on the individual microcontrollers that follow.

A pointer to a function is identified by

int (*function_ptr)();

The above declaration says that function_ptr is a pointer to a function that returns a type int. The arguments are not declared here. The parentheses surrounding *function_ptr are required. If they were not included, the declaration would declare function_ptr as a function that returns a pointer to the type int. If function_ptr is a valid pointer to a function, the function can be accessed by

(*function_ptr)(args);

The above declaration form can be used for arrays as well as functions. The declaration

char (*array_ptr)[];

Multidimensional Arrays

85

states that array_ptr is a pointer to an array of char. The declaration

char* array[];

states that array is an array of pointers to the type char. Although you will rarely find it used, these declarations can be combined to create very complicated declarations.

One construct from the general area of complicated declarations is so important to microcontroller code that it must be covered. C sup­ ports variable types called lvalues. As mentioned earlier, an lvalue is a type of variable that can be the destination for an assignment. Most variables in C are lvalues. Notable exceptions are function names and array names. If a program deals with a number that can be a memory address, it can be made accessible to the language by casting the ad­ dress to an appropriate type. For example, suppose that a special table is located at the address 0x1000 in memory. Further, suppose that the type to be stored at that address is an integer. Here, the code sequence

(int *) 0x1000

forces the number 0x1000 to be a pointer to a type int. Often this idea must be carried further, and the programmer wants to put a value into a specific address. The above representation is a pointer to the type int. Therefore, a value can be assigned to that int by

*(int *) 0x1000 = integer_value;

which will place integer_value into the location 0x1000 in the computer memory.

Frequently, control registers, data registers, and input/output port registers are placed at specific locations in memory. These register locations can be converted to tractable C names by use of the #de­ fine macro capability of C. Suppose that an I/O port is located at the address.

#define PORTA (*(char *) 0x1000)

allows the use of the name PORTA in the computer program. I define PORTA as a pseudo-variable. It is created by a macro expansion and such things are usually constants or function-like expansions. The above is neither. PORTA can be assigned to, or its value read. You can even do operations like


86Chapter 2 Advanced C Topics

PORTA |=0X80; if(PORTA & 0x6==0)

do something;

You can perform any operations with PORTA that you would want with any normal variable in the language. While PORTA has been generated by a macro expansion it also can be used as a variable. Thus, the title pseudo-variable. Unless there is a specific reason, though, I will call these macros “variables.”

The above pseudo-variable is of the type char, and its address is 0x1000. This capability is very useful in programming microcontrollers.

When programming microcontrollers, there are two reasons why it is necessary to be able to manipulate direct addresses in memory. Most high-level languages will not allow the programmer direct ac­ cess to specific memory locations. As seen above, C does allow the programmer to bend these rules enough to be able to store data into a specific memory address. Another feature that is highly desirable is to be able to place the address of a function at a specific address. This capability is necessary when implementing an interrupt service routine. When an interrupt occurs, the computer will stop its current operation, save at least the values contained in the status register and the program counter, and begin execution at an address contained in a vector location. Each interrupt will have a vector address, and each interrupt will require its own interrupt service routine. Program ini­ tialization when interrupts are involved will require that the program place the addresses of any interrupt service routines into the specific vector addresses for each interrupt.

A continuation of the above approach can be used in this case. There is a direct address in memory that is to receive the interrupt service routine address. Let’s think for a moment about what this address is. The address is going to contain the address of a function. The address itself is a pointer to a memory location. The contents of this location are a pointer that points to a function that is the interrupt service routine. All interrupt service routines are of the type void. Therefore, the vector address is a pointer to a pointer to a type void. To be able to place the value of the pointer to a type void into this location, we must assert one additional level of indirection to access the content of the specific memory loca­ tion. Therefore, the following line of code


Structures 87

*(void **) 0xfffc = isr;

places the beginning address of the function isr into the memory location 0xfffc.

A convenient method of executing this operation is to create a macro. The following macro definition works:

#define vector(isr, address) (*(void **)(address)=(isr))

Now the function call

vector(timer, 0xffd0);

will place the address of the function named timer into the loca­ tion 0xffd0 in the computer memory map. It is important that timer be defined as a function that returns a type void.

Structures

Another feature of C not found in many high-level languages is the structure. A structure is similar to an array, but far more general. A structure is a collection of one or more variables identified by a single name. The variables can be of different types. Structures are types in the sense that an int is a type. Therefore, if you have prop­ erly declared a structure, you may declare another structure of the same type. Examine the following structure:

struct person

{

char *name; char *address; char *city; char *state; char *zip

int height; int weight; float salary;

};

This structure contains some of the features that describe a per­ son. The person’s name and address are given as pointers to character strings. The person’s height and weight are integers, and the salary is

88 Chapter 2 Advanced C Topics

a floating-point number. The structure declaration must be followed by a semicolon. This combination of variables is created every time a structure of the type person is declared. The name person follow­ ing the struct declaration is called the structure tag or merely the tag. Tags are optional. If a tag is used, it may be used to declare other structures of the same type by

struct person ap,bp,cp;

Here ap,bp, and cp are structures of the type person. A single instance of a structure can be declared by

struct {....

} a;

In this case a is a structure with the elements defined between the braces. The elements that make up a structure are called its mem­ bers. Members of a structure can be accessed by appending a period followed by the member name to the structure name. For example, the name of the person represented by ap is accessed by

ap.name

and that person’s salary is

ap.salary

A pointer to a structure can be used. The pointer pperson is created by

struct person *pperson;

If a pointer to a structure is used, members of the structure can be accessed by the use of a special operator ->. This operator is created by use of the minus sign (-) followed by the right angle bracket character (>). The height of a person identified by the pointer pperson is accessed by

pperson->height

Arrays of structures are used, and when dealing with pointers to ar­ rays of structures, to increment the pointer will move the pointer to the next structure. If a program has

struct person people[20], *pp;

and pp is made to point at people[0] by

pp=people


Structures 89

then

people[1].name

is the same as

++pp->name

A structure can have another structure as a member element. Consider

struct point

{

int x; int y;

};

where point contains two integer elements, x and y. A circle can now be defined by

struct circle

{

struct point center; int radius;

};

In this case access to the members of center is by

circle.center.x

or

circle.center.y

Of course the radius is accessed by

circle.radius

Functions can take structures as arguments and they can return structures. For example, the function make_point() that follows returns a structure.

struct point make_point(int x, int y)

{

struct point hold;

hold.x=x;

90 Chapter 2 Advanced C Topics

hold.y=y; return hold;

}

Observe that the struct point is treated as a type with no diffi­ culty in this function. The return type is struct point, and within the body of the function hold is also a type struct point. The x argument passed to the function is placed in the x member of hold as is the y argument placed in the y member. Then the struct hold is returned to the calling function. All of these operations are legal.

Since structures create types, structures can have members that are structures. For example, suppose that the struct rect for a rectangle is defined as

struct rect

{

struct point p1; struct point p2;

};

Let’s outline a program that will inscribe a circle within a rect­ angle. The circle is tangent to the sides that make up the narrowest dimension of the rectangle.

/* Inscribe a circle in a rectangle */

/* first declare some useful structures */

struct point

{

int x; int y;

};

struct circle

{

struct point center; int radius;

};

struct rect

{

Structures 91

struct point p1; struct point p2;

};

/* here is a function to make a circle */ struct circle make_circle ( struct point ct, int rad )

{

struct circle temp;

temp.center = ct; temp.radius = rad; return temp;

}

/* some useful function like macros */ #define min(a,b) (((a)<(b)) ? (a) : (b)) #define abs(a) ((a)<0 ? -(a) : (a))

/* function prototypes */

void draw_rectangle(struct rect); void draw_circle(struct circle);

int main ( void )

{

struct circle cir; struct point center;

struct rect window = { {80,80},{600,400} }; int radius,xc,yc;

center.x = (window.p1.x+window.p2.x)/2; center.y = (window.p1.y+window.p2.y)/2;

xc = abs(window.p1.x -window.p2.x)/2; yc = abs(window.p1.y -window.p2.y)/2; radius = min(xc,yc);

cir=make_circle(center,radius);


92 Chapter 2 Advanced C Topics

draw_rectangle(window); draw_circle(cir); return 0;

}

At the beginning of the program, several important struct types are defined.These include a point, a rectangle, a circle, and a func­ tion to make circle given its center and its radius. Two macro definitions are needed. The first is the calculation for the minimum value of a and b and the second returns the absolute value of the argument. Two function prototypes are included. These functions will draw a rectangle and a circle to the screen, respectively.

Inside the main program cir is declared to be of the type struct circle, center is struct point , and window is of the type struct rect. When window is defined, it is initial­ ized to the values shown. This type of initialization is acceptable to structures as well as arrays. The rectangle is defined by two points. The point {80,80} is the lower lefthand corner of the rectangle, and the point {600,400} is the upper right corner. These locations are implementation dependent, and in some cases might represent the upper left corner {80,80} and the lower right corner {600,400}.

The center of the window is calculated by determining the aver­ age value of the x members of each point along with the average value of the y members. These values are the exact center of the rectangle. The center of the inscribed circle will lie at this point. The radius of the inscribed circle will be one-half the length of the short­ est dimension of the rectangle. The two potential value are calculated as xc and yc. Here the absolute value is used, because in general, it is impossible to know that the rectangle will be specified by the lower left hand corner in p1 and the upper right hand corner in p2. If these points were interchanged, negative values would be calculated. The selection of the positive result through the abs() macro avoids this problem. The final choice for radius is the minimum value of xc or yc.

The above calculations provide enough information to specify the circle, so cir is calculated as the return value from make_circle(). Finally, two compiler specific functions, draw_rectangle() and draw_circle(), are used to draw the calculated figures to the screen.

Structures 93

While it would not be difficult to execute these calculations without the use of structures, it is obvious that the structure formulation of the program makes a much simpler and easier to follow program. The vari­ ables and program elements are objects here rather than mere numbers.

C has a command called typedef. This command can rename a specified type for the convenience of the programmer. It does not create a new type, it merely renames an existing type. For example,

typedef int Miles; typedef char Byte; typedef int Word; typedef long Dword;

are all valid typedef statements. After the above invocations, a declaration

Miles m;

Byte a[20];

Word word;

Dword big;

would make m an int, a an array of 20 characters, word the type int, and big a long. All that has happened is that these types are a redefinition of the existing types. New types defined by typedefs are usually written with an upper case first letter. This is a tradition, not a requirement of the C language.

Structures used earlier could be modified by use of the typedef. Consider

typedef struct

{

int x; int y; } Point;

This typedef redefines the earlier struct point as Point. The judicious use of typedefs can make a program even easier to read than the simple use of structs. The program that follows is the same as that above where all of the structures are typedef new names.

/* Inscribe a circle in a rectangle */