Sunteți pe pagina 1din 30

Low Level C Programming

Low Level C Programming ________________________________________________ i


Introduction________________________________________________________________ 1
History __________________________________________________________________________ 1 Compilers _______________________________________________________________________ 1 Some C Basics____________________________________________________________________ 1

Variables __________________________________________________________________ 3
Types, Declarations, and Format Specifiers _____________________________________________ 3 Keyboard Input / Screen Output ______________________________________________________ 4 Type Conversions and Typecasting____________________________________________________ 5 Variable Scope ___________________________________________________________________ 6 Storage Class of Variables___________________________________________________________ 6

Functions __________________________________________________________________ 8
Writing and Using Functions ________________________________________________________ 8 Function Prototypes________________________________________________________________ 9 Pointers, Addresses, and Function Arguments ___________________________________________ 9

Preprocessor Directives and Header Files ______________________________________ 12


#include ________________________________________________________________________ 12 Header Files_____________________________________________________________________ 12 #define _________________________________________________________________________ 12

Program Control Statements _________________________________________________ 13


The while loop___________________________________________________________________ 13 The do-while loop ________________________________________________________________ 13 The for loop _____________________________________________________________________ 14 The if statement __________________________________________________________________ 14 The if-else statement ______________________________________________________________ 15 The if-else-if ladder _______________________________________________________________ 15 The switch statement ______________________________________________________________ 15

Hexadecimal, Decimal, and Binary ____________________________________________ 17


Conversions Between Base Systems __________________________________________________ 17 Signed Integers and 2s Compliment__________________________________________________ 18 Some Fun Notes About #s of Bits ___________________________________________________ 19

Operators_________________________________________________________________ 20
Arithmetic, Logical, and Bitwise Operators ____________________________________________ 20 Masking ________________________________________________________________________ 22

Cryptic ___________________________________________________________________ 24 Misc. Functions in Borland __________________________________________________ 25 PC/AT Computer Architecture _______________________________________________ 26

Introduction History
C was originally written in the early 1970s and implemented in 1978 by Dennis Richie at Bell Telephone Laboratories, Inc. It is associated with UNIX because the operating system was actually written in C. The language is sometimes called a system programming language, because of its usefulness in writing operating systems. Operating systems were originally written in low level languages like assembly language. C is a high level language with the necessary low level resources to deal with hardware. This property makes the modern C language a versatile tool. In 1983, the American National Standard Institute (ANSI) created a committee to provide a standard definition of the C programming language. They wrote the ANSI standard for C. Most C compilers implement the ANSI standard and more. Later, C++ was developed. It is a superset of the C programming language. It has all the features of C as well as additional ones. Most importantly C++ is uses object-oriented programming (OOP). Writing an OOP program is very different from writing a procedural program. Most common languages, including C, are procedural. You may however, write C programs for a C++ compiler, which is what we will do in this class.

Compilers
Programming languages can be divided into two different types: compiled and interpreted. Interpreters (e.g. original BASIC and MATLAB) proceed through a program by translating and then executing single instructions, one at a time. This is very easy when writing the code and debugging. In fact, many compiled languages will run in an interpreted mode in the integrated development environment (IDE) for debugging purposes. Many of you have probably used the single step mode of some IDE (e.g. the IDE for Visual Basic). Compiled programs must be translated into machine language before they can be executed. Compilers (such as C and JAVA) translate an entire program into machine language before executing any of the instructions. A compiler or interpreter is itself a computer program that accepts written code for an editor as input and generates a corresponding machine-language program as output. The original written code is called the source code, and the resulting machine-language program is called the object code. Another program, called a linker, operates on object code files and combines them into a resulting executable file (program.exe). This is known as linking. Since the program doesnt execute single instructions, one at a time, it is much faster and can be run outside of the IDE. When you generate an executable from source code you go through both the compiling and linking processes.

Some C Basics
C is case sensitive. This means that Dog, dog, DOG, dOg, and doG are all different identifiers in C. The word "main" is very important. It must appear once, and only once in every C program. It defines the main routine. This is the point where execution is begun when the program is executed. It does not have to be the first statement in the program but it must exist as the entry point for the program.

#include <stdio.h> void main(void) { printf(Here is your first C program) } // This is also a comment

/* This is a comment */;

Comments are delineated by /* */ and by // See the previous example code for examples of comments. A comment is any additional text that you add to your code for clarification of what is taking place. All comments are ignored by the compiler, so they do not add to the file size of the executable program. Neither do they affect the execution of the program. In ANSI C, comments begin with the sequence /* and are terminated by the sequence */. Some compilers also implement the // sequence. Everything to the right of the // sequence, on the same line, is a comment. Braces, { }, define blocks There are many types of blocks (i.e. groups of code) in C. They all are defined by { }. In the previous example the { } define the main function block. In the next example the if block is nested in (i.e. defined inside of) the main block. In other words the if block of code is part of the main routine. main() { int data; data = 2; if (data == 2) { // this is the start of main the if block printf("Data is now equal to 2\n"); data = 3; data = 2*3 + 5; } // this is the end of the if block } /* this is the end the main block */ // this is the start of the main block

The semicolon, ;, defines the end of a single statement In the previous example there are several statements that are followed by a semicolon. In C a statement does not have to be on a single line. Its end is determined by the semicolon rather than by the end of the line. For example, the following two statements are the same as far as the compiler is concerned. data = 2*3 + 5; data = 2*3 + 5;

Variables Types, Declarations, and Format Specifiers


Variables in C must be declared before they can be used. The declaration establishes the identifier and data type. ANSI C acknowledges that the size and range of the basic data types (and their various permutations) are implementation-specific and usually derive from the architecture and/or the operating system of the host computer. The table below shows common data types for most 32-bit C compilers. Table 1-1. Common Variable Types for most 32-bit C Compilers Type char unsigned char int unsigned int short int long (long int) float double Range -128 to 127 0 to 255 -2,147,483,648 to 2,147,483,647 0 to 4,294,967,295 -32,768 to 32,767 -2,147,483,648 to 2,147,483,647 3.4x10-38 to 3.4x1038 (7 digit precision) 1.7x10-308 to 1.7x10308 (15 digit precision) # of bits (# of bytes) 8 (1) 8 (1) 32 (4) 32 (4) 16 (2) 32 (4) 32 (4) 64 (8) format specifier %c %c %d %u %d %d %f %lf

This next example contains several declarations and format specifiers for printing. The printf function is the standard print function used to output to the display. A format specifier is preceded by the % sign, and the value following the quotes is substituted in its place in the printed output. main(){ / ************************ VARIABLE DECLARATIONS *************/ / *************************************************************/ int a; /* simple integer type */ long int b; /* long integer type */ short int c; /* short integer type */ unsigned int d; /* unsigned integer type */ char e; /* character type */ float f; /* floating point type */ double g; /* double precision floating point */ int int_array[5]; /* 5 element array of ints */ float float_array[4][4] /* 4 by 4 matrix of floats */ a = 1023; b = 2222; c = 123; d = 1234; e = 'X'; f = 3.14159; g = 3.1415926535898; int_array[0] = 18; flaot_array[0][3] = 55; printf("a = %d\n",a); /* decimal output */ printf("a = %x\n",a); /* hexadecimal output */ printf("b = %ld\n",b); /* decimal long output */ printf("c = %d\n",c); /* decimal short output */ printf("d = %u\n",d); /* unsigned output */ printf("e = %c\n",e); /* character output */ printf("f = %f\n",f); /* floating output */ printf("g = %f\n",g); /* double float output */ printf("a = %d\n",a); /* simple int output */ printf("a = %7d\n",a); /* use a field width of 7 */ printf("a = %-7d\n",a); /* left justify in field of 7 */ printf("\n"); printf("f = %f\n",f); /* simple float output */ printf("f = %12f\n",f); /* use field width of 12 */ printf("f = %12.3f\n",f); /* use 3 decimal places */ printf("f = %12.5f\n",f); /* use 5 decimal places */ printf("f = %-12.5f\n",f); /* left justify in field */

In the preceding program arrays were also introduced. Arrays of any variable type can be used in C. Multidimensional arrays are also used. Array indices in C range from 0 to one less than the length of the array.

Keyboard Input / Screen Output


For the time being we will diverge from our present topic of variables to discuss console I/O. In the previous example we used several examples of the printf function. The printf function is used to print formatted output to the screen. The function consists of two main parts: a format string and a variable argument list. The format string specifies what type of data will be output. The variable argument list supplies the data to be output. printf(control string, arg1, arg2, .......,argx); The control string is composed of text to be printed and format specifiers, with one format specifier for each printed variable. Each format specifier begins with a percent sign, %. See Table 1-1 for some of the format specifiers. The format specifier can include modifiers for number of places used, the number of places after the decimal point, and the justification (left or right) of the output. The best way to understand these is through experimentation. You may have noticed the \n at the end of control strings in the printf statements used to this point. This is called a special escape sequence. The most frequently used control sequence is the \n, the newline (carriage return, enter key, whatever). Table 1-2 lists some more of the escape sequence keys. Table 1-2. Special Escape Sequences \\ \b \ \n Backslash Backspace Double quotes Newline \t \a \v Tab Beep Vertical tab

Input data can be entered into the computer from a standard input device (keyboard) by means of the C library function scanf(). This function can be used to enter any combination of numerical values and single characters. When a program uses the scanf() function to get data, the user must press the enter key after the data is entered. This is different from getche() or getch(), which wait for a single key to be pressed. The general scanf() function has the following prarmeters: scanf(control string, arg1, arg2, arg3,..., argx); Table 1-3. Scanf() conversion codes. %c %e %h %s single character floating point value in exponential format hexadecimal integer string pointer %d %f %i %u single decimal integer floating point value Integer unsigned decimal integer

void main() { float age; float days; printf(How many years old are you? ); scanf(%f , &age); days = age*365; printf(\nYou are %.1f days old.\n,days);

As explained earlier getche() is another function for inputting data, but only one character. This function is similar to getch() which inputs a character but does not echo it back to the screen.

Type Conversions and Typecasting


Expressions are evaluated one operation at a time in the order of precedence for the operators. The result of each operation has a variable type. This type is determined by of the largest type of the operands (e.g. float is a larger type than int). For example, the resultant type of an addition between a float and an int is a float. Before the two numbers are added, the int variable is converted up to a float value. If the result of the addition is then set equal to an int variable, it will be truncated. The best way to be sure of type conversions is by testing them in your program. All the type conversion rules are not explicity listed here. However, the sample program below should help. This program also demonstrates initialization of variables in the declaration. #include <stdio.h> void main(void) { int two = 2, three = 3, intval; float fiveptfive = 5.5, floatval; floatval intval = floatval floatval floatval

//declarations with initializations // // // // // floatval inval is floatval floatval floatval is 1 is is is 1.833 6.5 (5.5 + 1) 6.5 (5.5 + 1) 7.0 (5.5 + 1.5)

= fiveptfive/three; fiveptfive/three; = fiveptfive + three/two; = fiveptfive + 3/2; = fiveptfive + 3.0/2;

Notice the difference between the last two results. In one result the division is an integer division because both values are ints. In the other the result, the division is floating point division because one of the operands is a float value. Type casting can be used to change the type of variable or expression during the evaluation of operators and when values are passed to functions. To type cast a variable or expression the desired type is put in parentheses before the variable or expression. Type casting is demonstrated in the program below. #include <stdio.h> void main(void) { int two = 2, three = 3; float fiveptfive = 5.5, floatval; floatval floatval floatval floatval floatval = = = = = fiveptfive/three; // floatval is 1.833 (int)fiveptfive/three; // floatval is 1.0 fiveptfive + three/two; // floatval is 6.5 (5.5 + 1) fiveptfive + (float)three/two; //floatval is 7.0 (5.5+1.5) fiveptfive + (float)(three/two); //floatval is 6.5 (5.5+1)

In the second statement, integer division is used because both operands are integers after the typecast is applied. In the fourth statement floating point division is used because one of the operands is a float. In the last statement, integer division is used because the type cast is applied to the expression after a division with two ints is performed.

Variable Scope
The scope of a variable is the part(s) of the program in which it is known. For example, variables that are declared in a function are only known in that function. These are local variables, and are not known in other functions. Actually the scope a local variable is the block in which it is declared. This includes main function block. A global variable is known throughout the program. It is declared outside of all blocks. The following program and discussion should help you understand scope. int g = 1; int func1(void); int func1(void); main() { int x; x = func1(); x = func2(); } int func1(void) { int output; output = g; return output; } int func2 (void) { int g = 3, x, y; x = 4; y = x + g; return y; } In the preceding code the variable g is a global variable. It is known in main and in func1. It can be modified and/or used in these these two functions. It is declared as a local variable in the func2 however. Therefore it cannot be modified or used here. The use of g in func2 refers to its own local variable. The variables x, y, and output are local variables. They are only known in their respective functions. The statement x = 4 in func2 does not affect the x in the main function.

// x is 1 // x is 7

Storage Class of Variables


The storage class of a local variable is either automatic or static. The default is automatic. An automatic variable is stored in a memory location that may be used to store other automatic variables also. This means that when you leave a function with a variable set at a certain value, it may not be the same value when you re-enter the function. Since automatic is the default you almost never see a variable explicitly declared as automatic. In contrast to automatic variables, static variables are stored in memory locations that are not used by other variables. This means the variable will remain the same even when you leave and re-enter the function that it is declared in. To declare a static variable, begin with the keyword static. In the following program, if number were not declared static then we would not be able to predict the printed output.

#include <stdio.h> int increment(void); void main(void) { printf(%d\n, increment() ); printf(%d\n, increment() ); printf(%d\n, increment() ); } int increment(void) { static int number = 0; number = number +1; return (number); }

// this prints 1 // this prints 2 // this prints 3

Functions
Subroutines, or functions as they are called in C, are used in almost all programming languages. A modular program is one that consists of subprograms that, when combined, create a working program. All subroutines are functions in C. There is no distinction made between those that do and those that do not return a value through the function name itself, like there is in Basic and Fortran.

Writing and Using Functions


The declaration of a function in C may be done in many ways. However, the following is a fairly standard way of declaring and writing a function. functiontype functionname( argument list) { LOCAL VARIABLE DECLARATIONS CODE FOR THE FUNCTION return returnvalue;

The functiontype is the type of variable that is returned through the functions name (e.g. x = func1()). If the functiontype is omitted then the default is int. If no value is returned through the function name then it should be declared void and the return statement at the end omitted. The functiontype is any valid variable type including arrays, structures, etc. The argument list is a list of the variables passed to the function including their type. Whether or not the argument is passed back to the calling function by changing the variable that is passed, depends on whether it is a pointer or not. This is discussed in the next section. If no arguments are passed then the word void can be substituted for the argument list. We have been using functions already and will continue to do so. Your understanding of them will be increased through the examples in this tutorial. The following is a simple example of the use of functions. #include <stdio.h> void nothing(void); float second_function(int a, int b); main(){ int x = 1; unsigned char y = 2; float one_half; nothing(); one_half = second_function(x,y); printf("Second_function made one_half = %f = %d / %d\n",one_half,x,y); // function prototypes

void nothing(void){ printf("\n\nThis is a function that does nothing but\n"); printf("print some lines of code.\n\n\n\n"); } float second_function(int a, int b){ // there is no code because this is a simple example return ((float)a/b); }

Function Prototypes
A prototype is a declaration of a function and its argument list without the actual code for the function. The example programs to this point have included prototypes. All functions should be prototyped before they are used for the first time. It is customary to do this at the top of the program or in a header file. We will discuss header files in a later section. If the function is declared (written) in the same file that it is used in, then the prototype is not required. However, prototyping in this case will increase the rigor of the compilers type checking and reduce the chances for problems. If the function being used is in another file or in library then a prototype must be included in the file that it is called in. Even standard functions, like the printf function, must be prototyped before they are used. You may have questioned the purpose line #include <stdio.h> in many of the previous examples. The header file stdio.h contains the prototypes of the standard input/output functions of C.

Pointers, Addresses, and Function Arguments


In C pointers are very important. Simply stated, a pointer is an address, a memory location. A pointer declaration is like any other variable declaration except that it is preceded by an asterick, *. For example, if a variable is declared float *fltptr then fltptr is a variable that contains the memory address of a floating point variable and *fltptr is the the variable at that location. The ampersand, &, is used to obtain the memory address of a variable. For example, if a variable is declared float fltvar then &fltvar is the address the floating point variable fltvar. Although the following program does not give any insight into the usefulness of pointers it does demonstrate the concepts of pointers and addresses. main() { int index,*pt1, *pt2; index = 39; pt1 = &index;

// index is an int var, pt1 and pt2 are pointers //to integers

// pt1 is the address of index and *pt1 is index

printf("The value is %d %d\n",index,*pt1); // prints 39 twice } *pt1 = 13; // index is now 13

The following two rules are very important when using pointers and must be thoroughly understood. 1. A variable name with an ampersand in front of it defines the address of the variable and therefore points to the variable. 2. A pointer with a "star", (i.e. asterick) in front of it refers to the value of the variable pointed to by the pointer. The pointer by itself is the memory address where the value is stored. In the example program, since we have assigned pointer, pt1, to address location of index, we can manipulate the value of index by using either the variable name itself, or by using *pt1. Anywhere in the program where we want to use the variable index, we could use the name *pt1 instead, since they are identical in meaning until pt1 is reassigned to the address of some other variable. To add a little intrigue to the system, we have another pointer defined in the example program, pt2. Since pt2 has not been assigned a value, it contains garbage. Assigning a value to the integer variable *pt2 changes an arbitrary memory location. This is, of course, very dangerous since we do not what this address is being used for. Pointers are admittedly a difficult concept. However, they are used extensively in all but the most trivial C programs. It is well worth your time to read the previous material until you understand it thoroughly.

A pointer must be defined to point to some type of variable. Following a proper definition, it cannot be used to point to any other type of variable or it will result in a "type incompatibility" error. The reason for this is easily understood if it is considered that different variable types use different amounts of memory. For example an int uses two bytes of memory whereas a float uses four bytes. Not all forms of arithmetic are permissible on the pointer value itself, only those things that make sense, considering that a pointer is an address somewhere in the computer. It would make sense to add a constant to an address, thereby moving it ahead in memory that number of places. Likewise, subtraction is permissible, moving it back some number of locations. Adding two pointers together would not make sense because absolute memory addresses are not additive. Pointer multiplication is also not allowed, as that would be a funny number. Pointers as Function Arguments Thus far we have been discussing pointers and addresses without demonstrating their usefulness. The most common use of pointers is in the passing of arguments to functions. In C, if a simple variable name is passed to a function then its value cannot be changed by the function. Only its value is passed (this is called pass by value). However, if the address of a variable is passed to the function then the variable can be changed by the function. If you want a function to change the value of a variable in its arrqument list then you must pass the address of the variable. The following program demonstrates the use of pointers as function arguments. #include <stdio.h> void first_function(int a, int b); void second_function(int *pa, int *pb); void main(){ int x = 0; int y = 0; first_function(x,y); second_function(&x, &y); }

// function prototypes

// x and y are not changed by this call // x and y are two after this call

void first_function(int a, int b){ int c; c = a + b; // c is zero (x + y from main) a = 1; // this does not change x and y b = 1; // in the main function } void second_function(int *pa, int *pb){ int c; c = *pa + *pb; // c is zero (x + y from main) *pa = 2; // this does changes x and y *pb = 2; // in the main function } Array Names as Pointers In C, an array variable name without any indices is a pointer to the the beginning of the array. For example, if an array is declared as char string[20] then string is the pointer (the address) to the first element of the array, a pointer to string[0]. However string[0] is the actual value contained in the first element. For all practical purposes string is a pointer. It does, however, have one restriction that a true pointer does not have; it cannot be changed like other pointers, and therefore always points to the same location. Given the preceding discussion it might be apparent that whenever an array is used as an argument to a function, it is a pass by reference argument. This means that the address is passed. The

10

following example should help illustrate the use of arrays as function arguments. Any of the three methods for declaring the array in the argument list that are shown below are valid. void first_function(int pa[10], int b); void second_function(int pa[], int b); void third_function(int *pa, int b); void main(){ int x[10], y; first_function(x,x[0]); // the pointer to x is passed and the value // of the tenth element is passed second_function(x,y); third_function(x,y); // function prototypes

void first_function(int pa[10], int b){ pa[1] = 1; // this changes the second element of x b = 1; // this does not change the first element of x } void second_function(int pa[], int b){ pa[1] = 1; // this changes the second element of x } void third_function(int *pa, int b){ pa[9] = 1; // this changes the last element of x } Memory-Mapped Register Access using Pointers Although it is nonstandard, and dangerous, it is possible to set a pointer to point at a specific memory address. This can be useful in computer architectures such as single-board computers and other similarly simple architectures where hardware devices are memory-mapped. This is one of the beauties (and responsibilities) of using C rather than a language that does not allow low-level programming. For example, suppose that a dsp (digital signal processor) card is being programmed and it has a digital output port whose register is mapped to the address 0x500017 by the hardware on the card. Then the following is an example of how to write a value out on this output port. Also, suppose that the card has an 4 counters that are used to keep track of 4 encoder inputs in hardware. These counter registers are mapped contiguously in memory at addresses 0x500030 to 0x500033. Then the following might is also an example of how to read the encoder counts. void main(){ int *DIGOUTD; // declare a pointer to be used to point the register int *QDCOUNT; // declare a pointer to be to point to the first counter int encoder_val[4]; // array to hold the encoder counts DIGOUTD=(int *)0x500017;//set pointer to the add of the register *DIGOUTD = 0x05; //set bits 0 and 2 of the output port to one QDCOUNT=(int *)0x500030; //set pointer to address of first counter for (i=0;i<4;i++) encoder_val[i] = QDCOUNT[i]; // read the encoders

In the example above it is very important that the programmer know the address of the register so that the pointer does not point to some important location other than the register. It is also important the programmer makes sure that the data type used for the pointer and the register are the same size.

11

Preprocessor Directives and Header Files


Preprocessor directives are performed before the file is compiled. They are not performed by the compiled program. They therefore do not affect the speed of the code. All preprocessor directives start with the # symbol. There are many preprocessor directives but the two most common are the #include and #define directives.

#include
The #include statement is a preprocessor directive that tells the compiler to merge the specified file into the current file. The contents of that file become part of the current file. This is like a big copy and paste. #include is most commonly used for header files, such as stdio.h. In previous programs the angle brackets have been used around the name of the file (e.g. #include <stdio.h> ). This tells the compiler to look in the standard library directory for the file. If you are including a file that is not located here, then quotations should be used and the full path should be specified. The following is an example of this. #include c:\mydir\myfile.h

Header Files
Header files typically contain function prototypes, global variables, and constants used by a library or program. For example the function prototype for the printf() is in stdio.h header file. Many compilers are smart enough to include the right standard header files in your program. Often however you may get an error of the effect, function funcname should have a prototype. This should tell you that you probably have not included the header file for the library containing the function, funcname. If you do not know which header file it is in, then the easiest solution is to use the context sensitive help that is available in most IDEs.

#define
The #define directive (sometimes called a macro) can be used to define symbolic constants within a C program. Constants are useful because they save memory for variables and also speed up the program in many cases. After you define an expression you may use it as often as you like. By convention, C programmers use uppercase characters for #define identifiers. Macro definitions are usually placed at the beginning of a file. The macro definition can be accessed from its point of definition to the end of the file. The following rules apply when you create macros: The name for the macro must follow the rules set aside for any other identifier in C. Most importantly the macro name cannot contain spaces. The macro definition should not be terminated by a semicolon unless you want the semicolon included in your replacement string. The following example demonstrates the use of constants. #define PI 3.1415 #define TWOPI 2*PI void main(void) { float circumference, area, radius = 5.0; circumference = TWOPI * radius; area = PI * radius * radius;

12

Program Control Statements The while loop


The while loop continues to loop while some condition is true. When the condition becomes false, the looping is discontinued. It therefore does just what it says it does, the name of the loop being very descriptive. The syntax of a while loop is as shown in the following example. The keyword "while" is followed by a conditional expression in parentheses, and a compound statement enclosed in braces. As long as the expression in parenthesis is true, all the statements within the braces will be executed. In this case, since the variable count is incremented by one every time the statements are executed, it will eventually reach 6, and the loop will be terminated. The program control will resume at the statement following the statements in braces. /* This is an example of a "while" loop */ main() { int count; count = 0; while (count < 6) { printf("The value of count is %d\n",count); count = count + 1; }

We will cover the conditional expression, the one in parentheses, later. Until then, simply accept the expressions for what you think they should do, and you will probably be correct. Several things must be pointed out regarding the while loop. First, if the variable count were initially set to any number greater than 5, the statements within the loop would not be executed at all, so it is possible to have a while loop that never executes. Secondly, if the variable were not incremented in the loop the loop would never terminate. Finally, if there is only one statement to be executed within the loop, it does not need braces but can stand alone, directly after the conditional.

The do-while loop


A variation of the while loop is illustrated in the next program. This program is nearly identical to the last one except that the loop begins with the reserved word "do", followed by a compound statement in braces, the reserved word "while", and the conditional expression in parentheses. The statements in the braces are executed repeatedly as long as the expression in parentheses is true. When the expression in parentheses becomes false, execution is terminated, and control passes to the statements following this statement. /* This is an example of a do-while loop */ main() { int i; i = 0; do { printf("The value of i is now %d\n",i); i = i + 1; } while (i < 5);

Several things must be pointed out regarding this statement. Since the test is done at the end of the loop, the statements in the braces will always be executed at least once. Secondly, if "i" were not

13

changed within the loop, the loop would never terminate, and hence the program would never terminate. Finally, just like for the while loop, if only one statement will be executed within the loop, no braces are required. It should come as no surprise to you that these loops can be nested. That is, one loop can be included within the compound statement of another loop. The nesting level has no limit.

The for loop


The for loop is really nothing new, it is simply a new way to describe the while loop. The for loop consists of the reserved word "for" followed by a rather large expression in parentheses. This expression is really composed of three fields separated by semi-colons. /* This is an example of a for loop */ main() { int index; for(index = 0;index < 6;index = index + 1) printf("The value of the index is %d\n",index);

The first field, the initializing field, contains the expression "index = 0. Any expressions in this field are executed prior to the first pass through the loop. There is essentially no limit as to what can go here, but good programming practice is to keep it simple. Several initializing statements can be placed in this field, separated by commas. The second field, in this case containing "index < 6", is the conditional. The test is done at the beginning of each pass through the loop. It can be any expression which will evaluate to a true or false. (More will be said about the actual value of true and false later.) The expression contained in the third field is executed each time the loop is executed, but it is not executed until after those statements in the main body of the loop are executed. This field, like the first, can also be composed of several operations separated by commas. Following the for() expression is any single or compound statement which will be executed as the body of the loop. In nearly any context in C, a simple statement can be replaced by a compound statement that will be treated as if it were a single statement as far as program control goes.

The if statement
The following program is an example of our first conditional branching statement, the if. Notice first, that there is a for loop with a compound statement containing two if statements. This is an example of how statements can be nested. It should be clear to you that each of the if statements will be tested 10 times. /* This is an example of the if and the if-else statements */ main(){ int data; for(data = 0;data < 10;data = data + 1) { if (data == 2) printf("Data is now equal to %d\n",data); if (data < 5) printf("Data is now %d, which is less than 5\n",data); else printf("Data is now %d, which is greater than 4\n",data); } } /* end of for loop */

14

Consider the first if statement. It starts with the keyword "if" followed by an expression in parentheses, the conditional. If the expression is evaluated and found to be true, the single statement following the if is executed, and if false, the statement is skipped. Here too, the single statement can be replaced by a compound statement. The expression "data == 2" is simply asking if the value of data is equal to 2, this will be explained in detail in the next chapter.

The if-else statement


In the previous example, the second if is followed by the reserved word "else." This simply says that if the conditional evaluates to true, the first expression is executed, otherwise the expression following the else is executed. Thus, one of the two expressions will always be executed, whereas in the first if the single expression was either executed or skipped. Both will find many uses in your C programming efforts. Compile and run this program to see if it does what you expect. #include <stdio.h> #include <conio.h> void main() { printf(Type a key on the keyboard:\n); if (getch() == y) printf(You pressed the y key); else printf(\nYou did not press the y key);

The if-else-if ladder


The if-else-if ladder looks like this: if (conditional#1) statement#1; else if (conditional#2) statement#2; else if (conditional#3) statement#3; . . . else last statement; Statement #1 is executed if the first conditional is true, and no other statements are executed. Statement #2 is executed if the first conditional is false and the second is true, and no other statements are executed. This continues until the last statement, following the else keyword, which is executed if none of the conditionals are true. The else is optional.

The switch statement


The switch statement is a very powerful function for use in menus. It begins with the keyword "switch" followed by a variable in parentheses, which is the switching variable, in this case "ch". As many cases as desired are then enclosed within a pair of braces. The reserved word "case" is used to begin each case, followed by the value of the switching variable, a colon, and the statements to be executed.

15

#include <stdio.h> #include <conio.h> void dog(void); void main(void) { char ch; printf("*************Main Menu***************\n"); printf("1 Go to number one\n"); printf("2 Go to number two\n"); printf("3 Enter the function dog\n"); printf("Enter you choice now "); ch=getche(); switch (ch) { case '1': printf("\nYou have chosen number one\n"); break; case '2': printf("\nYou have chosen number two\n"); break; case '3': dog(); break; } } void dog() { printf("\nThis is the function dog called from case 3"); } Once an entry point is found, statements will be executed until a break is found or until the program drops through the bottom of the switch braces. In case 3 the program goes into the function dog(), then the program returns to main().

16

Hexadecimal, Decimal, and Binary


Numbers can be expressed in different base systems. The numeric format you are most familiar with is the base 10, which is called the Decimal system. It uses the digits 0 to 9. Binary is base 2, and Hexadecimal is base 16. The reason for studying Binary and Hexadecimal numbers is that ultimately all data is stored as bits (1s or 0s) in a computer. This is a Binary representation. Hexadecimal is more closely related to Binary than is decimal because 16 is an even power of 2 while 10 is not. Table 1-4. Number Bases Type Decimal Hexadecimal Binary Base 10 16 2 Digits 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A (10), B, C, D, E, F (15) 0, 1

The notation for a base 16 number is a subscript of 16 after the number or an h behind the last digit. For distinction, sometimes base 10 is delineated with subscript of 10 behind or a d behind the last digit. Often no distinction is made for a base 10 number. Binary numbers use a b at the end of the number or the subscript 2. Examples: 35 = 35d = 3510 = 23h = 2316 107 = 107d = 10710 = 6Bh = 6B16

Example: 6 = 6d = 610 = 0110b = 01102 The key to understanding different base systems is in the placeholders. The first digit in Decimal is the 1s spot, the second is the 10s spot, the third is the 100s spot, etc. The following figure demonstrates the concept of placeholders using the same number in the three different bases.

3674
1000s = 10 100s = 102 10s = 101 1s = 100
3

256s = 162 16s = 161 1s = 160

E5A16

3674 = 3*1000 + 6*100 + 7*10 + 4

3674 = 14*256 + 5*16 + 10

1110 0101 10102


2048s = 211 1024s = 210 512s = 29 256s = 28

3674 = 2048+1024+512+64+16+8+2

1s = 20 2s = 21 4s = 22 8s = 23 16s = 24 32s = 25 64s = 26 128s = 27

Figure 1-1. Placeholders in Decimal, Hex, and Binary

Conversions Between Base Systems


The conversions between the number bases will be demonstrated through examples using the number 367410 = E5A16 = 1110 0101 10102, which is shown in Figure 1-1.

17

Hex to Decimal and Binary to Decimal This is demonstrated in Figure 1-1. It simply involves multiplication of the digit in the placeholder by the value of the placeholder and summing each of these values. Binary to Hex and Hex to Binary Binary and Hex have a simple relationship because 16 is 24. Each Hex digit corresponds to 4 bits. To convert from Binary to Hex, four bits at a time are converted to single Hex digit, starting with the right most four bits. Example: Convert 1110 0101 10102 to Hex 01012 = 4 + 1 = 5 = 516 10102 = 8 + 2 = 10 = A16 1110 0101 10102 = E5A16 11102 = 8 + 4 + 2 = 14 = E16

To convert from Hex to Binary this process is reversed. Each Hex digit is converted to four bits. Example: Convert E5A16 to Binary 516 = 5 = 4 + 1 = 01012 A16 = 10 = 8 + 2 = 10102 E5A16 = 1110 0101 10102 E16 = 12 = 8 + 4 + 2 = 11102

To convert from Binary to Decimal it is often easier to convert to Hex, and then to Decimal. Decimal to Hex and Decimal to Binary Converting Decimal to Hex and Decimal to Binary is the most difficult, you must use modulo division. Example: Convert 3674 to Hex 3674 / 163 = 3674/4096 = 0 with remainder of 3674 3674 / 162 = 3674/256 = 14 with remainder of 90 90 / 161 = 90/16) =5 with remainder 10 10/160 = 16/1 = 10 with remainder 0 3674 = E5A16

14 = E16 5 = 516 10 = A16

Decimal to Binary conversions can be accomplished in a similar manner or by converting Decimal to Hexadecimal and then Hexadecimal to Binary.

Signed Integers and 2s Compliment


Sometimes it is very important to pay attention to whether a variable is declared as a signed or unsigned int, char , or long int, especially when you are dealing with hardware devices. In most computers a signed integer uses the most significant bit (MSB) as a sign bit. If that bit is zero the number is positive, and if the bit is 1 then the number is negative. A rule of thumb is that if you are manipulating data in a bit wise manner then you should declare the variable unsigned in C. Example: 127 and -127 as a signed int 127 = 0000 0000 0111 11112 -127 = 1111 1111 1000 00012 MSB indicates positive MSB indicates negative

You may have noticed that the -127 is not simply 127 with the 16th bit set to 1. Integer math is usually faster in a computer if 2s compliments are used for negative numbers. The 2s compliment of a number is the negative of that number. To get a 2s compliment of a number, take the compliment (reverse all the bits) and add one.

18

Example: 2s compliment of 127 is -127 1111 1111 1000 0000 + 1 1111 1111 1000 0001 Example: 2s compliment of -127 is 127 0000 0000 0111 1110 + 1 0000 0000 0111 1111

Compliment of 127 Add one -127 Compliment of -127 Add one 127

Some Fun Notes About #s of Bits


Table 1-5. Notes About #s of Bits # of bits 4 8 10 12 16 20 24 30 Name nibble byte/char Kilobyte (K) --Megabyte (M) Gigabyte (G) Range(decimal) 0 to (24 - 1) = 15 0 to (28 - 1) = 255 0 to (210 - 1) = 1023 0 to 4095 0 to 65,535 0 to 1,048,575 0 to (16M -1) 0 to (1024M -1) Notes:

A 360 KB disk has 360*1024 = 368,640 bytes (not 360,000) Commonly A/D and D/As are 12 bit Some A/D and D/As are 16 bit M = K*K = 10242 = 1,048,576 (sometimes 1,000,000) The 286, which has 24 bit addressing, has 16MB limit on Ram G = K*M = 10243= 1,073,741,824 (sometimes 1,000,000,000)

19

Operators Arithmetic, Logical, and Bitwise Operators


C uses the four arithmetic operators that are common in most other programming languages. The operands acted on by the arithmetic operators must represent numeric values. Division on one integer value by another is referred to as integer division, and is usually faster than floating point division. Integer division truncates the result. It does not round. The % operator is the remainder after integer division. Table 1-6. Arithmetic and Relational Operators + * / % pow(x,y) Arithmetic Operators Addition Subtraction Multiplication Division modulo division xy (actually a function) < > == != >= <= Relational Operators less than greater than equal to not equal to greater than or equal to less than or equal to

Relational operators are symbols used to compare two values. If the values compare correctly according to the relational operator, the expression is considered true: otherwise it is considered false. #include <stdio.h> void main() { int i=7; printf(i printf(i printf(i printf(i printf(i printf(i printf(i }

is equal to: %d\n\n,i); < 5 is %d\n, i<5); > 4 is %d\n, i>4); == 6 is %d\n, i==6); != 7 is %d\n, i!=7); <= 10 is %d\n, i<=10); >= 6 is %d\n, i>=6);

/*prints /*prints /*prints /*prints /*prints /*prints

0 1 0 0 1 1

(false)*/ (true) */ (false)*/ (false)*/ (true) */ (true) */

Before continuing with operators, a clarification of true and false in C is useful. Any nonzero value is considered true in C, including negative numbers and character values. Only a zero value is considered false. Therefore if a number is used as a conditional, the conditional is considered true for any value other than zero. The following program should help to demonstrate this. #include <stdio.h> void main() { int i=7, j=0; if (i) printf(true); if (j) printf(false); if (j-i) printf(true); if (0.000000001) printf(true); while (1); }

/*prints true*/ /*prints nothing */ /*prints true*/ /*prints true*/ /*endless loop */

20

In C a distinction is made between logical and bitwise operators. Logical operators operate on the logical value of an expression while bitwise operators operate on each bit of a value. This should become clear in the examples that follow.

Table 1-7. Logical and Bitwise Operators. && || ! Logical Operators Logical AND Logical OR Logical NOT & | ~ >> << ^ Bitwise Operators AND OR Compliment Shift Right Shift Left Exclusive OR (XOR)

The logical AND, as well as logical OR operators work on two operands to return a logical value based on the operands. The logical NOT operator works on a single operand. #include <stdio.h> main() { int i=3; int j=0; printf("Examples of logical expressions\n"); printf("-------------------------------\n"); printf("i && j %d\n",i&&j); printf("i || j %d\n",i||j); printf("!I %d\n",!i); printf("!j %d\n",!j); printf("i>0) && (j<7) %d\n",(i>0)&&(j<7)); printf("(i<0) || (j<7) %d\n",(i<0)||(j<7)); printf("!(i>5) || (j>0) %d\n",!(i>5)||(j>0));

/*outputs /*outputs /*outputs /*outputs /*outputs /*outputs /*outputs

0*/ 1*/ 0*/ 1*/ 1*/ 1*/ 1*/

The bitwise operators grant you low level control of values through C. Bitwise operators refer to the testing, setting, or shifting of the actual bits in a number. Even though the operations are performed on a single bit basis, an entire byte or integer variable is operated on in one instruction. The following examples are bitwise operations on unsigned char (byte) values. The same concepts apply to all integer type variables. Example: Bitwise AND 1010 0110 1100 0111 1000 0110 Example: Bitwise OR 1010 0110 1100 0111 1110 0111 Example: Compliment 1010 0110 0101 1001 Example: Right Shift 1010 0110 0000 1010 166 AND 199 equals 134 166 OR 199 equals 231 NOT 166 equals 89 166 right shift 4 equals 10

21

Example: Left Shift 1010 0110 1001 1000 #include <stdio.h>

166 left shift 2 equals 152

main() { unsigned char i=166, j=199; printf("Examples of bitwise operations \n"); printf("-------------------------------\n"); printf("i & j %d\n",i&j); printf("i | j %d\n",i|j); printf("~i %d\n",~i); printf("i>>4 %d\n",i>>4); printf("i<<20) %d\n",i<<2);

/*outputs /*outputs /*outputs /*outputs /*outputs

134*/ 231*/ 89*/ 10*/ 152*/

Masking
The general concept of masking is used in many I/O programming problems because hardware data is often manipulated at the bit level. For the following examples, suppose that a dsp (digital signal processor) card is being programmed and it has a digital output port whose register is mapped to the address 0x500017 by the hardware on the card. Also suppose that it has a digital input port whose register is mapped to the address 0x500015. Suppose that we wish to make bit 0 of the digital output port a zero, without changing any of the other bits. Then the following is an example of how that might be accomplished. Also, suppose that there are limit switches connected to the digital input port and we wanted to determine if switches connected to bits 4 or 5 of were being pushed (assume pushed=1). void main(){ int *DIGOUTD; // pointer to point the output ports register int *DIGINB; // pointer to point the input ports register int port_val; // temporary storage variable DIGOUTD=(int *)0x500017;//set pointer to the add of the output port DIGINB=(int *)0x500017; //set pointer to the add of the input port port_val = *DIGOUTD; // read current value on output port port_val = port_val & 0xfffffffe; // make bit 0 a zero *DIGOUTD = port_val; // write the new value out to the port port_val = *DIGINB; // read the input port if (port_val & 0x18) {perform some action}

In this example the mask, 0xfffe, is used to manipulate bit 0 specifically, without changing any of the other bits. This is done with the bitwise AND operator. xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx1 1111 1111 1111 1111 1111 1111 1111 1110 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx0 or xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx0 1111 1111 1111 1111 1111 1111 1111 1110 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx0 port_val AND 0xfffffffe will also sets bit 0 to 0 port_val AND 0xfffffffe will only sets bit 0 to 0

22

It doesnt matter what bit 0 was to begin with, zero or one, it is zero after the masking operation of the bitwise and with 0xfe. Furthermore, the other 31 bits are not affected by this operation. If they were one, then they are still one. If they were zero, then they are still zero. The second masking operation can be viewed as follows. Remember we want to determine if either bit 4 or bit 5 is one? Here we use a bitwise AND with 0x18. Remember that anything other than zero is true in C. xxxx xxxx xxxx xxxx xxxx xxxx xxx1 0xxx 0000 0000 0000 0000 0000 0000 0001 1000 0000 0000 0000 0000 0000 0000 0001 0000 or xxxx xxxx xxxx xxxx xxxx xxxx xxx0 0xxx 0000 0000 0000 0000 0000 0000 0001 1000 0000 0000 0000 0000 0000 0000 0000 0000 port_val AND 0x18 equals false port_val AND 0x18 equals true

As another example suppose we wanted to set bits 4 and 5 to one? We could use a bitwise OR with 0x18. It will set bits 4 and 5 to one, no matter what they are currently, without affecting the other bits. xxxx xxxx xxxx xxxx xxxx xxxx xxx1 1xxx 0000 0000 0000 0000 0000 0000 0001 1000 xxxx xxxx xxxx xxxx xxxx xxxx xxx1 1xxx or xxxx xxxx xxxx xxxx xxxx xxxx xxx0 0xxx 0000 0000 0000 0000 0000 0000 0001 1000 xxxx xxxx xxxx xxxx xxxx xxxx xxx1 1xxx port_val OR 0x18 sets bits 4 and 5 = 1 port_val OR 0x18 sets bits 4 and 5 = 1

There are many other examples of how to use masking. It just requires careful thought about what your are trying to achieve. How would you determine if either bit 4 or bit 8 were zero?

23

Cryptic
There are a few constructs used in C that may seem a little strange, but they greatly increase the efficiency of the compiled code and are used extensively by experienced C programmers. In the following program, several examples of these are given. main() { int x = 0,y = 2,z = 1025; float a = 0.0,b = 3.14159,c = -37.234; /* incrementing */ x = x + 1; x++; ++x; z = y++; z = ++y; /* decrementing */ y = y - 1; y--; --y; y = 3; z = y--; z = --y; /* arithmetic op */ a = a + 12; a += 12; a *= 3.2; a -= b; a /= 10.0; /* conditional expression */ a = (b >= 3.0 ? 2.0 : 10.5 ); if (b >= 3.0) a = 2.0; else a = 10.5; c = (a > b?a:b); c = (a > b?b:a); /* c will have the max of a or b */ /* c will have the min of a or b */ /* This increments x */ /* This increments x */ /* This increments x */ /* z = 2, y = 3 */ /* z = 4, y = 4 */ /* This decrements y */ /* This decrements y */ /* This decrements y */ /* z = 3, y = 2 */ /* z = 1, y = 1 */ /* /* /* /* /* This This This This This adds 12 to a */ adds 12 more to a */ multiplies a by 3.2 */ subtracts b from a */ divides a by 10.0 */

/* This expression is equivalent */ /* to the following if-else */

24

Misc. Functions in Borland


clrscr() used to clear the entire active text window and locate the cursor in the upper left corner. Located in the conio.h include file. kbhit() returns zero if no key has been hit and nonzero otherwise. This function is very slow and should not be used in any loop were speed is a consideration.

25

PC/AT Computer Architecture


A simplified diagram of the PC/AT computer architecture is shown in the Figure 1-2. All types of peripheral devices communicate with the CPU using some computer bus. Physically, a bus is simply a common set of conductors (bus lines) that connect all the devices together. A bus standard also includes specifications such as clock speeds and timing diagrams for sequential events. Some of the lines are used to transmit the data, and others are used to transmit the address (a number that identifies a particular device or memory location). Each address corresponds to one byte of data. Some of the lines are control signals, like the clock pulse and the Interrupt Request (IRQ) lines. The peripheral devices watch the address and control lines, and respond when their address is transmitted. They then take the data from the data bus, if the CPU is writing to them, or put data onto the data bus if the CPU is reading from them. In most computer architectures, the bus can be broken up into an I/O bus and a memory bus. The memory bus is faster, and the speed depends on the processor (e.g. a 20 MHz 80386 uses a 20 MHz bus clock and a 66 MHz 80486 uses a 33 MHz bus clock). The I/O bus is slow, and the speed is usually a standard for the architecture. The PC/AT uses an 8 MHz bus clock for the I/O bus. In the PC/AT the I/O bus is differentiated from memory bus by a few control lines. The two busses may use some of the same addresses and data lines, but a few control lines differentiate whether the address placed on the bus by the CPU is for I/O devices or for devices on the memory bus. Most I/O devices are given I/O bus addresses (e.g. the serial communications ports, the parallel printer ports, the interrupt and DMA controllers, and computer cards used in data acquisition and control). However, a few of the faster I/O devices will be placed on the memory bus (e.g. the video card, hard drive controller, and a few of the faster data acquisition cards).

CPU

(8259) Interrupt Controller

NMI

...
Memory & Fast I/O Devices
Memory Bus

IRQ Lines

BUS

I/O Bus

I/0 Devices

...
(8237) DMA Controller
Figure 1-2. I/O Architecture of the PC/AT

DRQ Lines

Hold

When installing an I/O card (such as a data acquisition card) a base address must be chosen for it. For the PC/AT I/O addresses fall into the following three ranges: 016 - 20016 20116 - 3FF16 40016 - 7FF16 Reserved for use on motherboard most common (compatible with the PC/XT I/O cards) less common (not compatible with PC/XT I/O cards)

26

The card will take a certain number of registers (I/O bytes). These addresses must not conflict with any other device. If only one I/O card, other than the standard I/O devices like the serial ports, is installed, then the most common base address is 30016. Table 1-8 shows the typical I/O address map for the PC/AT. The highlighted spaces are the best bets in choosing the addresses for an I/O card.

Table 1-8. Typical PC/AT Address Map Hex Address Range 000-01F 020-03F 040-05F 060-06F 070-07F 080-09F 0A0-0BF 0C0-0DF 0F0 0F1 0F8-0FF 1F0-1F8 200-207 208-277 (112 bytes) 278-27F 280-2F7 (120 bytes) 2F8-2FF 300-31F (32 bytes) 320-35F (64 bytes) 360-36F 378-37F 380-38F 390-39F (16 bytes) 3A0-3AF 3B0-3BF 3C0-3CF 3D0-3DF 3E0-3EF (16 bytes) 3F0-3F7 3F8-3FF 400-7FF (1024 bytes) Use DMA controller 1, 8237A-5 Interrupt controller 1, 8259A, master Timer, 8254, 2 8042 (keyboard) Real-time clock, NMI mask DMA page registers, 74LS612 Interrupt controller 2, 8259A DMA controller 2, 8237A-5 Clear math coprocessor busy Reset math coprocessor Math coprocessor Fixed disk Game I/O Not Used Parallel printer port 2 Not Used Serial port 2 Prototype card Not Used Reserved Parallel printer port 1 SDLC, bisynchronous 2 Not Used Bisynchronous Monochrome display and printer adapter Reserved Color/graphics monitor adapter Not Used Diskette controller Serial port 1 Not Used if all I/O devices decode the tenth address bit

27

AT Extension to the XT Card Slots 8 Additional Data Lines IRQ Lines 10,11,14,15 DRQ Lines 0, 5-7

XT Card Slots 8 Bit Data Lines 32 Address Lines IRQ Lines 3-7, 9 DRQ Lines 1-3

PCI Card Slots

Rear Panel Of the PC

Figure 1-3. Card Edge Connectors in the PC/AT Two features of the PC/AT bus standard that are often used by I/O peripherals are interrupts and DMA transfers. When you are installing an I/O device, you are often forced to choose an interrupt number and/or a DMA number. INTERRUPTS Interrupts are used by I/O devices to get the attention of the CPU. When the interrupt controller receives an interrupt signal on one the interrupt request (IRQ) lines, it interrupts the CPU, which then stops what it is doing, and services the interrupt. This is a very powerful tool in I/O applications. For example, the CPU may set up the I/O device to perform a task, like collecting data. When the I/O device is done, it will interrupt the CPU, and the CPU will get the data from the device. This allows the CPU to perform other tasks while the I/O device is at work, rather than setting and twiddling its thumbs. On the card edge connectors of the PC/AT there are 10 IRQ available, numbered 3-7, 9-11, and 14-15. If only the 8 bit data bus is used then 3-7 and 9 are available. The 8 IRQ lines added in the PC/AT extension of the PC/XT bus, are cascaded through IRQ 2. The interrupt structure of the PC/AT is shown in Table 1-9. The IRQ that are likely candidates to be used when inserting a peripheral device are highlighted. DMA Direct Memory Access is the transfer of data directly between the I/O devices and memory, bypassing the CPU. In DMA transfers the DMA controller takes control of the bus after receiving a DMA request from the I/O device on a specific DRQ line. This transfer can be faster than using the CPU to transfer data if large chunks of data are being moved to/from memory. However, if the I/O device uses the full 16 bit I/O data bus of the PC/AT then it is faster (and easier) to use the REP INSW assembly language command to transfer data to memory. DMA transfers are becoming a thing of the past.

On the card edge connectors of the PC/AT there are 7 DRQ available, numbered 0-3, and 5-7. If only the 8 bit data bus is used then 1-3 are available. The 4 DRQ lines of the older PC/XT standard are cascaded through through DRQ 4 added in the PC/AT extension. The DMA structure of the PC/AT is

28

shown in Table 1-10. The DRQ that are likely candidates to be used when inserting a peripheral device are highlighted. Table 1-9. PC/AT Interrupt Structure Interrupt Controller and IRQ # 8259 #1 8259 #2 IRQ 0 IRQ 1 IRQ 2 IRQ 8 IRQ 9 IRQ 10 IRQ 11 IRQ 12 IRQ 13 IRQ 14 IRQ 15 IRQ 3 IRQ 4 IRQ 5 IRQ 6 IRQ 7 Vector Number 8 9 NA 16 17 18 19 20 21 22 23 11 12 13 14 15 Use Timer 0 (Time of Day Clock - 18.2 Hz) Keyboard Cascade Interrupt from 8259 #2 CMOS real-time clock Replaces IRQ 2 Not Used Not Used Not Used Math Coprocessor Fixed Disk Controller Not Used Serial Port 2 if installed Serial Port 1 Parallel Port 2 if installed Diskette Controller Parallel Port 1

Table 1-10. PC/AT Interrupt Structure DMA Controller and DRQ # 8237 #2 8237 #1 DRQ 4 DRQ 0 DRQ 1 DRQ 2 DRQ 3 DRQ 5 DRQ 6 DRQ 7 Page Register Address NA 8716 8316 8116 8216 8B16 8916 9A16 Use Cascade Input from 8237 #1 Not Used SDLC adapter (if installed) Diskette controller Not Used Not Used Not Used Not Used

29

S-ar putea să vă placă și