Vous êtes sur la page 1sur 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 2’s 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

i
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 doesn’t 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 a comment */;
} // This is also a comment

1
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() { // this is the start of the main block


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 */

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;

2
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 Range # of bits (# of bytes) format specifier
char -128 to 127 8 (1) %c
unsigned char 0 to 255 8 (1) %c
int -2,147,483,648 to 2,147,483,647 32 (4) %d
unsigned int 0 to 4,294,967,295 32 (4) %u
short int -32,768 to 32,767 16 (2) %d
long (long int) -2,147,483,648 to 2,147,483,647 32 (4) %d
float 3.4x10-38 to 3.4x1038 (7 digit precision) 32 (4) %f
double 1.7x10-308 to 1.7x10308 (15 digit precision) 64 (8) %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 */
}

3
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


\\ Backslash \t Tab
\b Backspace \a Beep
\” Double quotes \v Vertical tab
\n Newline

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 single character %d single decimal integer
%e floating point value in exponential format %f floating point value
%h hexadecimal integer %i Integer
%s string pointer %u 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);
}

4
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; //declarations with initializations
float fiveptfive = 5.5, floatval;

floatval = fiveptfive/three; // floatval is 1.833


intval = fiveptfive/three; // inval is 1
floatval = fiveptfive + three/two; // floatval is 6.5 (5.5 + 1)
floatval = fiveptfive + 3/2; // floatval is 6.5 (5.5 + 1)
floatval = fiveptfive + 3.0/2; // floatval is 7.0 (5.5 + 1.5)
}

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 = fiveptfive/three; // floatval is 1.833


floatval = (int)fiveptfive/three; // floatval is 1.0
floatval = fiveptfive + three/two; // floatval is 6.5 (5.5 + 1)
floatval = fiveptfive + (float)three/two; //floatval is 7.0 (5.5+1.5)
floatval = 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.

5
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 is 1
x = func2(); // x is 7
}

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.

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.

6
#include <stdio.h>
int increment(void);

void main(void)
{
printf(“%d\n”, increment() ); // this prints 1
printf(“%d\n”, increment() ); // this prints 2
printf(“%d\n”, increment() ); // this prints 3
}

int increment(void)
{
static int number = 0;
number = number +1;
return (number);
}

7
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); // function prototypes
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);
}

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

8
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 is an int var’, pt1 and pt2 are pointers
//to integers

index = 39;
pt1 = &index; // 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.

9
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); // function prototypes
void second_function(int *pa, int *pb);

void main(){
int x = 0;
int y = 0;
first_function(x,y); // x and y are not changed by this call
second_function(&x, &y); // 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); // function prototypes


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

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 IDE’s.

#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 (1’s or 0’s) 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 Base Digits
Decimal 10 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Hexadecimal 16 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A (10), B, C, D, E, F (15)
Binary 2 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 1’s spot, the second is the 10’s spot, the third is the 100’s spot, etc. The following figure
demonstrates the concept of placeholders using the same number in the three different bases.

3674 E5A16
1000’s = 10 3 256’s = 162
100’s = 102 16’s = 161
10’s = 101
1’s = 160
1’s = 100
3674 = 3*1000 + 6*100 + 7*10 + 4 3674 = 14*256 + 5*16 + 10

1110 0101 10102


1’s = 20
2048’s = 211 2’s = 21
1024’s = 210 4’s = 22
512’s = 29 8’s = 23
256’s = 28 16’s = 24
32’s = 25
64’s = 26
3674 = 2048+1024+512+64+16+8+2 128’s = 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


10102 = 8 + 2 = 10 = A16 01012 = 4 + 1 = 5 = 516 11102 = 8 + 4 + 2 = 14 = E16
1110 0101 10102 = E5A16

To convert from Hex to Binary this process is reversed. Each Hex digit is converted to four bits.

Example: Convert E5A16 to Binary


A16 = 10 = 8 + 2 = 10102 516 = 5 = 4 + 1 = 01012 E16 = 12 = 8 + 4 + 2 = 11102
E5A16 = 1110 0101 10102

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 14 = E16
90 / 161 = 90/16) =5 with remainder 10 5 = 516
10/160 = 16/1 = 10 with remainder 0 10 = A16
3674 = E5A16

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 2’s 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 MSB indicates positive
-127 = 1111 1111 1000 00012 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 2’s compliments are used for negative numbers. The 2’s compliment of a number is
the negative of that number. To get a 2’s compliment of a number, take the compliment (reverse all the
bits) and add one.

18
Example: 2’s compliment of 127 is -127
1111 1111 1000 0000 Compliment of 127
+ 1 Add one
1111 1111 1000 0001 -127

Example: 2’s compliment of -127 is 127


0000 0000 0111 1110 Compliment of -127
+ 1 Add one
0000 0000 0111 1111 127

Some Fun Notes About #’s of Bits


Table 1-5. Notes About #’s of Bits
# of bits Name Range(decimal) Notes:
4 nibble 0 to (24 - 1) = 15
8 byte/char 0 to (28 - 1) = 255
10 Kilobyte (K) 0 to (210 - 1) = 1023 A 360 KB disk has 360*1024 = 368,640 bytes (not 360,000)
12 -- 0 to 4095 Commonly A/D and D/A’s are 12 bit
16 -- 0 to 65,535 Some A/D and D/A’s are 16 bit
20 Megabyte (M) 0 to 1,048,575 M = K*K = 10242 = 1,048,576 (sometimes 1,000,000)
24 0 to (16M -1) The 286, which has 24 bit addressing, has 16MB limit on Ram
30 Gigabyte (G) 0 to (1024M -1) 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
Arithmetic Operators Relational Operators
+ Addition < less than
- Subtraction > greater than
* Multiplication == equal to
/ Division != not equal to
% modulo division >= greater than or equal to
pow(x,y) xy (actually a function) <= 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 is equal to: %d\n\n”,i);
printf(“i < 5 is %d\n”, i<5); /*prints 0 (false)*/
printf(“i > 4 is %d\n”, i>4); /*prints 1 (true) */
printf(“i == 6 is %d\n”, i==6); /*prints 0 (false)*/
printf(“i != 7 is %d\n”, i!=7); /*prints 0 (false)*/
printf(“i <= 10 is %d\n”, i<=10); /*prints 1 (true) */
printf(“i >= 6 is %d\n”, i>=6); /*prints 1 (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”); /*prints “true”*/
if (j) printf(“false”); /*prints nothing */
if (j-i) printf(“true”); /*prints “true”*/
if (0.000000001) printf(“true”); /*prints “true”*/
while (1); /*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 Bitwise Operators
&& Logical AND & AND
|| Logical OR | OR
! Logical NOT ~ 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); /*outputs 0*/
printf("i || j %d\n",i||j); /*outputs 1*/
printf("!I %d\n",!i); /*outputs 0*/
printf("!j %d\n",!j); /*outputs 1*/
printf("i>0) && (j<7) %d\n",(i>0)&&(j<7)); /*outputs 1*/
printf("(i<0) || (j<7) %d\n",(i<0)||(j<7)); /*outputs 1*/
printf("!(i>5) || (j>0) %d\n",!(i>5)||(j>0)); /*outputs 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 166 AND
1100 0111 199 equals
1000 0110 134

Example: Bitwise OR
1010 0110 166 OR
1100 0111 199 equals
1110 0111 231

Example: Compliment
1010 0110 NOT 166
0101 1001 equals 89

Example: Right Shift


1010 0110 166 right shift 4
0000 1010 equals 10

21
Example: Left Shift
1010 0110 166 left shift 2
1001 1000 equals 152

#include <stdio.h>

main()
{
unsigned char i=166, j=199;

printf("Examples of bitwise operations \n");


printf("-------------------------------\n");
printf("i & j %d\n",i&j); /*outputs 134*/
printf("i | j %d\n",i|j); /*outputs 231*/
printf("~i %d\n",~i); /*outputs 89*/
printf("i>>4 %d\n",i>>4); /*outputs 10*/
printf("i<<20) %d\n",i<<2); /*outputs 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 port’s register
int *DIGINB; // pointer to point the input port’s 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 port_val AND
1111 1111 1111 1111 1111 1111 1111 1110 0xfffffffe will
xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx0 only sets bit 0 to 0

or

xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx0 port_val AND
1111 1111 1111 1111 1111 1111 1111 1110 0xfffffffe will
xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx0 also sets bit 0 to 0

22
It doesn’t 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 port_val AND
0000 0000 0000 0000 0000 0000 0001 1000 0x18 equals
0000 0000 0000 0000 0000 0000 0001 0000 true

or

xxxx xxxx xxxx xxxx xxxx xxxx xxx0 0xxx port_val AND
0000 0000 0000 0000 0000 0000 0001 1000 0x18 equals
0000 0000 0000 0000 0000 0000 0000 0000 false

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 port_val OR


0000 0000 0000 0000 0000 0000 0001 1000 0x18 sets
xxxx xxxx xxxx xxxx xxxx xxxx xxx1 1xxx bits 4 and 5 = 1

or

xxxx xxxx xxxx xxxx xxxx xxxx xxx0 0xxx port_val OR


0000 0000 0000 0000 0000 0000 0001 1000 0x18 sets
xxxx xxxx xxxx xxxx xxxx xxxx xxx1 1xxx 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; /* This increments x */
x++; /* This increments x */
++x; /* This increments x */

z = y++; /* z = 2, y = 3 */
z = ++y; /* z = 4, y = 4 */

/* decrementing */
y = y - 1; /* This decrements y */
y--; /* This decrements y */
--y; /* This decrements y */

y = 3;
z = y--; /* z = 3, y = 2 */
z = --y; /* z = 1, y = 1 */

/* arithmetic op */
a = a + 12; /* This adds 12 to a */
a += 12; /* This adds 12 more to a */
a *= 3.2; /* This multiplies a by 3.2 */
a -= b; /* This subtracts b from a */
a /= 10.0; /* This divides a by 10.0 */

/* conditional expression */
a = (b >= 3.0 ? 2.0 : 10.5 ); /* This expression is equivalent */
/* to the following if-else */

if (b >= 3.0)
a = 2.0;
else
a = 10.5;

c = (a > b?a:b); /* c will have the max of a or b */


c = (a > b?b:a); /* c will have the min of a or b */
}

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 NMI


Controller
... IRQ Lines
BUS

Memory & Memory Bus I/O Bus I/0 Devices


Fast I/O Devices

... DRQ Lines

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

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 Reserved for use on motherboard


20116 - 3FF16 most common (compatible with the PC/XT I/O cards)
40016 - 7FF16 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 Use
000-01F DMA controller 1, 8237A-5
020-03F Interrupt controller 1, 8259A, master
040-05F Timer, 8254, 2
060-06F 8042 (keyboard)
070-07F Real-time clock, NMI mask
080-09F DMA page registers, 74LS612
0A0-0BF Interrupt controller 2, 8259A
0C0-0DF DMA controller 2, 8237A-5
0F0 Clear math coprocessor busy
0F1 Reset math coprocessor
0F8-0FF Math coprocessor
1F0-1F8 Fixed disk
200-207 Game I/O
208-277 (112 bytes) Not Used
278-27F Parallel printer port 2
280-2F7 (120 bytes) Not Used
2F8-2FF Serial port 2
300-31F (32 bytes) Prototype card
320-35F (64 bytes) Not Used
360-36F Reserved
378-37F Parallel printer port 1
380-38F SDLC, bisynchronous 2
390-39F (16 bytes) Not Used
3A0-3AF Bisynchronous
3B0-3BF Monochrome display and printer adapter
3C0-3CF Reserved
3D0-3DF Color/graphics monitor adapter
3E0-3EF (16 bytes) Not Used
3F0-3F7 Diskette controller
3F8-3FF Serial port 1
400-7FF (1024 bytes) 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 # Vector
8259 #1 8259 #2 Number Use
IRQ 0 8 Timer 0 (Time of Day Clock - 18.2 Hz)
IRQ 1 9 Keyboard
IRQ 2 NA Cascade Interrupt from 8259 #2
IRQ 8 16 CMOS real-time clock
IRQ 9 17 Replaces IRQ 2
IRQ 10 18 Not Used
IRQ 11 19 Not Used
IRQ 12 20 Not Used
IRQ 13 21 Math Coprocessor
IRQ 14 22 Fixed Disk Controller
IRQ 15 23 Not Used
IRQ 3 11 Serial Port 2 if installed
IRQ 4 12 Serial Port 1
IRQ 5 13 Parallel Port 2 if installed
IRQ 6 14 Diskette Controller
IRQ 7 15 Parallel Port 1

Table 1-10. PC/AT Interrupt Structure


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

29

Vous aimerez peut-être aussi