C and Labwindows
C and Labwindows
Table of Contents
TABLE OF CONTENTS .............................................................................................................................................. III FOREWORD ............................................................................................................................................................. VI ACKNOWLEDGMENTS.............................................................................................................................................. VI PREVIOUS FOREWORD ............................................................................................................................................ VI ACKNOWLEDGMENTS.............................................................................................................................................. VI
MS QUICKC EDITOR & COMPILER ....................................................................................................... 17 2.1. 2.2. INTRODUCTION: COMPUTER LANGUAGES ................................................................................................. 17 COMPILER OVERVIEW ................................................................................................................................ 19
3.
INTRODUCTION TO C ................................................................................................................................ 21 3.1. 3.2. 3.3. 3.4. 3.5. 3.6. 3.7. COMMENT STATEMENTS ............................................................................................................................ 22 INCLUDE STATEMENTS & INCLUDE FILES .................................................................................................. 23 DEFINE STATEMENTS ................................................................................................................................. 24 FUNCTION DECLARATIONS ......................................................................................................................... 24 GLOBAL VARIABLES .................................................................................................................................. 25 MAIN( ) ...................................................................................................................................................... 25 FUNCTIONS ................................................................................................................................................ 25
4.
VARIABLES ................................................................................................................................................... 29 4.1. 4.2. 4.3. 4.4. TYPE AND RANGE OF VARIABLES .............................................................................................................. 29 DECLARATION OF VARIABLES .................................................................................................................... 29 SCOPE OF VARIABLES ................................................................................................................................ 30 READING AND PRINTING VARIABLES.......................................................................................................... 32
5.
C OPERATORS............................................................................................................................................. 37 5.1. 5.2. 5.3. 5.4. 5.5. ARITHMETIC OPERATORS ........................................................................................................................... 37 RELATIONAL OPERATORS .......................................................................................................................... 38 LOGICAL OPERATORS................................................................................................................................. 38 BITWISE LOGICAL OPERATORS................................................................................................................... 39 SHIFT OPERATORS...................................................................................................................................... 40
6. 7.
BRANCHING INSTRUCTIONS................................................................................................................... 41 LOOP INSTRUCTIONS ................................................................................................................................ 44 7.1. 7.2. 7.3. FOR LOOPS ............................................................................................................................................... 44 WHILE AND DO LOOPS ............................................................................................................................ 45 NESTED LOOPS .......................................................................................................................................... 46 iii
8. 9.
ARRAYS.......................................................................................................................................................... 47 POINTERS ...................................................................................................................................................... 50 9.1. 9.2. 9.3. POINTERS AND VARIABLES ........................................................................................................................ 50 POINTERS AND ARRAYS ............................................................................................................................. 51 POINTERS, ARRAYS AND FUNCTIONS ......................................................................................................... 52 STRUCTURES............................................................................................................................................ 57 DATA FILES............................................................................................................................................... 59
10. 11.
11.1. ASCII AND BINARY DATA REPRESENTATION .............................................................................................. 59 11.2. FILES: GENERAL ......................................................................................................................................... 60 11.3. OPENING AND CLOSING A DATA FILE ..................................................................................................... 60 11.4. WRITING TO AN ASCII DATA FILE ......................................................................................................... 61 11.5. READING AN ASCII DATA FILE ............................................................................................................. 61 11.5. WRITING EXCEL FILES ........................................................................................................................... 62 11.6. WRITING AND READING A BINARY DATA FILE ....................................................................................... 63 12. 13. GRAPHICS.................................................................................................................................................. 65 ADDITIONAL INFORMATIONABOUT C PROGRAMMING ........................................................... 65
PART 2: LABWINDOWS..................................................................................67
1. INTRODUCTION.............................................................................................................................................. 69 2. DOS / SINGLE TASK OPERATING SYSTEM ............................................................................................. 70 3. WINDOWS OPERATING SYSTEM............................................................................................................... 71 3.1. PROCESS AND THREADS.................................................................................................................................. 71 3.2. MULTITASKING AND SCHEDULING .................................................................................................................. 71 3.3. MESSAGES AND WINDOWS ............................................................................................................................. 72 3.4. EVENT DRIVEN PROGRAMMING ...................................................................................................................... 72 4. LABWINDOWS CONCEPTS .......................................................................................................................... 73 4.1. GRAPHICAL USER INTERFACE (GUI) .............................................................................................................. 73 4.2. USER EVENT HANDLER AND CALLBACK FUNCTIONS ...................................................................................... 74 4.3. CONTROL VS. DISPLAY PANELS OR INPUT VS. OUTPUT PANELS ...................................................................... 75 5. LW SOFTWARE: EXAMPLE 1 ...................................................................................................................... 77 5.1. LW COMPONENTS OVERVIEW ........................................................................................................................ 77 5.2. EXAMPLE 1: GETTING STARTED...................................................................................................................... 77 5.3. START LABWINDOWS ..................................................................................................................................... 77 5.4. PROJECT WINDOW .......................................................................................................................................... 77 5.5. UIR-EDITOR ................................................................................................................................................... 78 5.6. GENERATING SKELETON C-CODE ................................................................................................................... 80 5.7. C-COMPILER ................................................................................................................................................... 80 5.8. CHANGING THE APPEARANCE OF THE GUI...................................................................................................... 82 5.9. EXPLANATION OF THE C-CODE ....................................................................................................................... 82 5.10. FLOWCHART OF THE C-CODE ........................................................................................................................ 84 5.11. CONCLUSION................................................................................................................................................. 85 6. EXAMPLE 2: CONTROLS AND INPUTS ...................................................................................................... 86 iv
6.1. CONTROLS PANELS OVERVIEW ....................................................................................................................... 86 6.2. ADDING A BINARY SWITCH ............................................................................................................................. 86 6.3. ADDING SKELETON C-CODE FOR THE NEW CALLBACK FUNCTION ................................................................. 87 7. EXAMPLE 3: OUTPUT AND THE SETCTRLVAL FUNCTION ................................................................ 90 7.1. DISPLAY PANELS OVERVIEW .......................................................................................................................... 90 7.2. ADDING A DISPLAY ........................................................................................................................................ 90 7.3. LW LIBRARY UTILITY .................................................................................................................................... 91 8. EXAMPLE 4: INPUT AND THE GETCTRLVAL FUNCTION .................................................................. 95 8.1. THE GETCTRLVAL FUNCTION......................................................................................................................... 95 8.2. DECLARING VARIABLES THROUGH THE LIBRARY UTILITY .............................................................................. 96 8.3. ADJUSTING THE CODE IN THE CALLBACK FUNCTION ...................................................................................... 96 9. EXAMPLE 5: TIMER....................................................................................................................................... 98 9.1. ADDING A TIMER TO YOUR GUI ..................................................................................................................... 98 9.2. ADDING CODE TO THE TIMER CALLBACK FUNCTION ...................................................................................... 99 9.3. MULTIPLE TIMERS......................................................................................................................................... 101 9.4. TIME INTERVALS........................................................................................................................................... 102 10. EXAMPLE 6: READING AND SETTING PANEL ATTRIBUTES......................................................... 103 10.1. ADDING A CONTROL KNOB ......................................................................................................................... 103 10.2. SETTING THE CONTROL ATTRIBUTE ............................................................................................................ 104 11. CONCLUSION AND CONVENTIONS........................................................................................................ 108 11.1. BEGINNING A NEW PROJECT ....................................................................................................................... 108 11.2. C-NAMING CONVENTIONS .......................................................................................................................... 108 APPENDIX............................................................................................................................................................ 109 TABLE 1: PREDEFINED FUNCTIONS ................................................................................................................... 109 TABLE 2A: OPERATORS BY CATEGORY............................................................................................................. 111 TABLE 2B: OPERATORS BY PRECEDENCE ......................................................................................................... 112 TABLE 3: 'PRINTF( )' TYPE SPECIFIERS AND ESCAPE SEQUENCES ........................................................................ 113
Foreword
The first part of this handout was originally used to familiarize students with DOS, C and the QuickC compiler. Starting with winter quarter 1998, the DOS operating system was replaced with WindowsNT 4.0 and the QuickC compiler was replaced with National Instrument's LabWindows C-compiler. Therefore, you will find sections in the first parts of this manual outdated while others are still relevant. What follows is a brief list of the chapters in the first part and how they relate to the course. Chapter 1 covers DOS and computer hardware. If you are unfamiliar with computers it may be useful to glance at the hardware section but you may skip the rest of chapter 1 about DOS entirely. I have included it as a reference because there are times when you may have to fall back on DOS, for example if you should work on an older machine, when you are setting up a computer or if you have a "sick" machine. Chapter 2 covers various computer languages and explains some basic concepts about compilers. It will be helpful if you glance briefly at it. Chapters 3 through 11 cover standard ANSI C concepts. You should read and understand chapters 3, 4, 5, 6, 7, 8 and 11 and you may want to glance at chapters 9 and 10 to get a general idea.
Acknowledgments
My thanks to Prof. Weyhmann and Jon Huber for their suggestions, proofreading and for providing various figures. Kurt Wick 12/2/97
C and LabWindows 00b.doc 11/2/2000 5:52 PM
Previous Foreword
This handout provides an overview of the IBM disk operating system and of the C programming language. It is intended for people with little or no prior knowledge of IBM personal computers or the C programming language. Students in the past have commented that the section on C programming is incomplete and that it should be expanded. We emphasize that this manual is NOT meant to be a complete reference covering all aspects of C; it is instead meant as an introduction to C programming, explaining the most often used expressions. The reason for not writing a more extensive C manual is that plenty of good (and not-sogood) books covering the C language already exist. (A list of the better ones is given in chapter 13.) Most of these are either very brief and are intended as a reference for people familiar with the subject, or they are written for the novice and are usually so large that important information is lost in details. When learning the language, do not try memorize details about it; instead, learn how to find the corresponding information using the "Help" utility (see section 2.2 for details).
Acknowledgments
Prof. Keith Ruddick, Prof. Earl Peterson and Philip Johnson contributed many ideas and put in a lot of effort to make the manual more understandable and readable. Thanks also to James Flaten for his rigorous proofreading skills.
Copyrights
The copyrights reside with the Regents of the University of Minnesota, Minneapolis, MN, November 2000. vi
1. DOS
Figure 1.1. 5 1/4" Disk: unfortunately they are still used today (probably because they are very cheap). They also make mediocre Frisbees. Finally, quickly becoming the most popular disk today, (probably because they were designed to fit in a shirt pocket) are the 3 1/2" disks (usually sold in blue). They are available with 720 KB capability (doubledensity) or 1.44 MB (high-density). (To determine whether you are using a 1.44 MB or 720 KB disk, count the number of 1/4" square holes in the disk; 1.44 MB disk have 2 holes, 720 KB disk have only one.)
Figure 1.2. 3 1/2" disks: the disk on the left is a 720 KB disk while the one on the right holds 1.44 MB. Our computers have a 3 1/2" drive that can read and write to 1.44 MB and 720 KB disks. In the labs, we will be using mostly 3 1/2", 1.44 MB disks. Other memory storage devices not mentioned previously (or used in our lab) are tape drives, CD ROM drives and other optical drives such as WORM drives (write once, read many times).
In this case the computer attempted to make A the default drive but was not able to read or write to the default drive because there was probably no disk in drive A:. Put a floppy disk in drive A: and type R for retry.
10
1.2.4. Directories
Now that you know how to select a disk drive, you want to be able to find out what has been stored on the disk in your present default drive. Enter:
DIR
(note, you can use upper and/or lower case letters for any DOS commands, they are case-insensitive). You should see a listing of all the files in your root directory, including the date and time that each file was either created or last modified. Given the size of today's hard disks, it is possible to store a few thousand files on a hard disk. Though it would be no problem for the computer to find a particular file, it would be very annoying for the user to read through the entire directory. Therefore, files are usually organized in directories and subdirectories in an upside-down tree structure (the root is on top and the branches on the bottom). For example, some of the directories on our hard drive C: are organized in the following way: Root Directory (contains some system files) | |---DOS (contains dos utilities) | |---QC25 (only contains C subdirectories) | |---- BIN (contains C compiler files) | |---- INCLUDE (contains all C "include" files) | |---- LIB (contains C libraries) | ---- SAMPLES (contains C programs | |----MAP (contains trackball utilities) | (any other directories) Each disk must have a root directory. A root directory may contain any number of files and subdirectories and each subdirectory may contain files and further subdirectories and so on. To display all the directories on your harddrive enter:
TREE
The collection of subdirectories leading to a file is often referred to as a 'path' For example, the path to a file in subdirectory 'sample' would be: \qc25\samples.
The first command selects drive C: as the default drive; the second command, which is short for "change directory", puts us in the root directory. While each directory has a unique name, the root directory is denoted by the backslash symbol \. The computer will reply with:
c:\>
11
1. DOS / 1.2. Computer Hardware Overview which means a) that our default drive is c: and b) that we are currently in the root directory of the default drive. Typing dir again we notice that some of the filenames end with <DIR>, indicating subdirectories, such as:
DOS <DIR> 9/26/90 3:05p QC25 <DIR> 11/13/90 2:55p
To move to a subdirectory enter 'cd' again and the entire path name to the subdirectory. For example, typing:
cd \qc25\samples
will move us from the root directory through subdirectory 'QC2' into sub-subdirectory 'SAMPLES'. Note, while the very first backslash in the path statement always indicates that the path begins at the root directory, the subsequent backslashes are used merely as separators. If you want to move from a subdirectory to the directory directly above, enter:
cd ..
For example, if your current default directory is qc2\samples, entering cd .. will change the current default directory to qc2. Clearly, you could have accomplished the same thing by entering: cd \qc25.
where md stands for "make directory." You can create any number of subdirectories as long as the directory under which the subdirectory will reside already exists; otherwise you must create it first. For example if you want to create directory ANYMARY under STUDENTS under the root directory, you probably have to create directory STUDENTS first and then directory ANYMARY.
12
rd \students\anymary rd \students
Omitting the first line will result in the following error message:
Invalid path, not directory, or directory not empty.
To change default disk drive, enter drive letter and colon: example: To change a directory enter CD and path: example: To create a directory enter MD and path: example: To delete a directory enter RD and path: example:
13
1.3.1. FORMAT:
Newly purchased disks are usually not formatted; therefore, prior to its first use, every floppy disk must be "formatted" or "initialized". The formatting process erases and verifies every sector on the floppy disk; control information and various indexes that DOS uses to organize and store data are written to the floppy disk. The most basic format command is: FORMAT [drive] where drive stands for the identification letter of the floppy disk drive in which the disk to be formatted has been placed. For example to format your 3 1/2", 1.44 MB floppy disk, put the disk into drive A; and type: format a: and follow the on-screen instructions. Though formatting is a very efficient way to get rid of data no longer needed, always remember that formatting erases the entire disk content! Always use the format command with caution. Typing the wrong drive letter can have disastrous consequences! The format command will always format a disk to the maximum capability of the disk drive. Hence, if you try to format a 3 1/2", 720KB in our 3 1/2", 1.44MB floppy drive, you would get an "Invalid Media or Bad Track" error message; to format a 720KB disk use:
format a: /F:720
1.3.2. DIR:
The dir command lists the files in a directory. The most general form of this command is: DIR [drive][path][filename] /W /P where: [drive] specifies the disk drive on which the directory to be listed is located ([drive] is optional if it is the same as the default drive) [path] is path to the directory to be listed (again, [path] is optional if directory is already the default directory) if [filename] is omitted, all files in that directory will be listed, if [filename] is used, only the file matching that name will be displayed, if it exists. /W is optional and lists files in the directory horizontally (useful if there are many files in a directory) /P is optional; pauses when the listing reaches the bottom of the screen. Some command such as DIR and COPY, that require a filename can also be used with the two "wildcard" characters "*" and "?". Replacing a character in a filename or extension with one or more "?" means that any character can occupy that space. A "*" in a filename means that any character can occupy that space and all the following character spaces in the filename or extension.
14
1.3.3. COPY:
The copy command copies one or more files from one directory to a different directory on the same or a different drive. The most general usage is: copy [drive1][path1]filename1 [drive2][path2][filename2] where: [drive1] and [path1] are the drive and path of the directory where the source file, file1 is located. [drive1] and [path1] are optional if you are already in the directory where file1 is located. filename1 is the complete name (filename + extension) of the file to be copied and is mandatory. filename1 can be used in combination with wildcard characters. [drive2] [path2] are the drive and path of the directory to which the source file is to be copied. Again, if you are already located in the directory to which the file is to be copied, the path and drive specifications are optional. [filename2] is the name of the copy of the file. If it is specified, it can be different than the original name; if it is omitted, the name of the original file will be used. Examples:
copy test09.c finalkw.c copy test09.c a: copy muons2?.c a: copy m* c:\garbage copies file test09.c to a new file named finalkw.c in the same directory copies file test09.c to floppy drive A:, maintaining the original name copies all files beginning with name muons2 and any one character and with extension c to drive a:, maintains the original filenames copies all files beginning with the letter 'm' to subdirectory garbage on drive C:
15
1. DOS / 1.4. Hotkeys where: [drive] and [path] specify the drive and path where the file to be deleted is located and are optional if the file is located in the current default directory and drive. The complete filename (filename and extension) is required. Wildcard characters such as "*" and "?" can be used. Note: DEL and ERASE can be used interchangeably. Examples:
del test.* del a:\*.* del test0?.c deletes all files in current directory with name test and any extension deletes all files in the root directory on drive a: deletes all files in current directory beginning with name test0 and any one character plus extension .c
1.3.5. TYPE
Type displays the content of an ASCII or text file. TYPE [drive][path]filename where: [drive] and [path] specify the drive and path where the file to be deleted is located and are optional if the file is located in the current default directory and drive. The complete filename (filename and extension) is required. Wildcard characters are not allowed. If the file specified is not an ASCII file, DOS will attempt to display the file content until it finds an end-of-file marker. Usually, output from such a file results in the screen filling up with garbage and the loudspeaker beeping. To print the content of a file on a printer, use: TYPE [drive][path]filename > LPT1 Examples:
type a:readme.txt type a:readme.txt > lpt1 displays content of file readme.txt on screen prints content of file readme.txt on printer
1.4. Hotkeys
DOS has some specials keys reserved to interrupt a program or to reboot the computer. Usually they are a combination of keys which have to be pressed at the same time. For example CTRL-S means that you hold down the CTRL key at the same time as the S key. Here is a list of some of the keys: CTRL-S: will stop output to the screen temporarily; hitting any key will resume the program (useful in combination with the DIR command). CTRL-Q: resumes after a CTRL-S was encountered CTRL-C: will terminate a program or process (if CTRL-C doesn't work try CTRL-BREAK) CTRL-ALT-DEL: stops every process and reboots the entire machine; all data not saved previously is lost. Using CTRL-ALT-DEL is almost identical to turning the computer off and then on again.
16
17
Name COBOL RPG FORTRAN ALGOL 58 APL BASIC Pascal Ada MODULA-2 C LISP SNOBOL 4 PROLOG FORTH Smalltalk 80
Application Business Business Scientific Scientific Scientific General General Scientific Scientific System Special Special Special General Special
Year 1959 1962 1954 1958 1962 1964 1971 1979 1979 1975 1960 1963 1970 1974 1980
Comments Used in business applications; ANSI Standard Used for producing business reports Still (one) of the most popular scientific languages Useful in mathematical problem solving Scientific applications Used on micro computers Structured language, used on micro computers Multiprocessing language ANSI Standard Used in artificial intelligence Artificial intelligence Object oriented language for graphical windows
Table of currently used high level languages. Computer languages can be grouped according to 'high-level' or 'low-level' languages. Low-level languages follow closely the instruction set of the original machine language. They depend on the specific CPU used and they are not very transportable (i.e. they don't work with different CPUs). They are executed very efficiently by the computer but are less efficient to the programmer. The quality (speed, reliability) depends almost entirely on the skills of the programmer. Assembly language is an example of a low level language. High level languages have very little in common with the basic machine language instruction sets. They call on large libraries of subroutines to execute very complex instructions. With a suitable set of libraries, programs can be used on different computers. Very complex tasks can quickly be programmed in these languages but the execution of the actual program can be slow and depends to a large degree on the capabilities of the actual compiler. The most common high level languages are: Pascal, FORTRAN and BASIC.
18
User
1: User Writes Source Code ex.: TEST.C
Computer
The first step in creating a program always requires the writing of the source code. The source code is a plain text file containing all your programming instructions. To create the source code you use a text editor or word processor. We will be using the text editor that is included with the QuickC compiler program though you could use any text editor that you prefer. Remember, the source code is just a plain text file. This means, at this stage the compiler does not care at all what you are writing; as far as the compiler is concerned, you might as well be writing a letter to your grandma. In the second step, the compiler examines your source code very carefully and tries to make sense out of everything that it encounters in the source code file. If the compiler encounters any instructions that are not part of the C language or that it does not recognize, it will print an error message and abort the compilation and return you to your editor. Correct your mistakes and then compile again. If your program is free of syntax errors, the compiler automatically writes an object file (extension .obj) to your (hard) disk; the object file is a non executable binary file that is needed later to create an executable file. Note, though the compiler checks for mistakes in your program it can not check whether your program will work correctly. Try to think of the compiler as an English teacher; the teacher can make sure that the spelling and grammar will be correct but cannot necessarily say whether the statement being made is correct.
19
2. EDITORS & COMPILERS / 2.2. Compiler Overview In the third step, the compiler combines the object file with various libraries and creates an executable file i.e. your final program. In this step, C statements are replaced with corresponding machine language statements. Remember from chapter 1 that executable files have extension .EXE Fourth, you are able to "run" or execute your program. While working on a program you will run your program from within the QuickC environment; this way when the program terminates it will return you automatically to the QuickC editor allowing you to make changes easily. Once your program is perfect, you can run it directly from DOS from any IBM compatible computer. As a (temporary) last step, if your program does not behave exactly the way you anticipated, you need to fix it or to "debug" it. You will return to the editor where there are various tools at your disposal to monitor your program while it is running. Don't try to rely too much on the old computer saying that "any undocumented feature is a bug and hence, any documented bug a planned feature."
20
3. Introduction To C / 2.2.
Compiler Overview
3. INTRODUCTION TO C
Every C program must at least contain the following elements:
main( ) { }
Generally, a full size C program may consist of the following elements: a) Comment Statements b) Include Files c) Define Statements d) Function Declarations e) Global Variable Declarations f) Main Program g) Additional Function Definitions A sample program containing all of these elements is listed below.
/* This program doesn't do anything /* though it will run /* Version 1.0 KW 1/1/90 #include <stdio.h> INCLUDE FILES #include <physics.h> */ */ */ /* COMMENT STATEMENTS */
/* */
/* DEFINE STATEMENTS
*/
double My_first_function(short value, float other_value); /* FUNCTION DECLARATIONS float Mysecondfunction(short x); unsigned short some_number; short main() { short a, b, any; short x = 5; short y = 7; float c = .33; double y_total; a = x * y; y_total = My_first_function( x, c); /* more statements */ } /* GLOBAL VARIABLES /* MAIN PROGRAM */ */
*/
*/
21
3. Introduction To C / 3.1.
Comment Statements
double ax; ax = some_number * somevalue / other_value; return( ax ); } float Mysecondfunction( short x ) { float a; a = SOMEOTHERCONSTANT*x; return ( a ); }
Except for the comment statements, keep the elements in the order shown above. Note two important C conventions: 1: unlike DOS, C is case sensitive. For example, if you declare the two variables, x and X, C will assume that they are distinct variables! 2: C ignores blank spaces and carriage returns; the only reason we use them in our source code is to make the code more readable. Finally, keep in mind that the program execution always starts at 'main'.
Remember that everything between the beginning comment statement '/*' and the ending one '*/' will be ignored by C. Hence, if you should ever forget to enter the ending comment statement, then C will consider everything else in your program as a comment, including the program itself! A different form of comment statements has lately been used in C. Instead of using the standard convention, some people prefer to use double slashes, '//', a convention borrowed from C++.
/* standard ANSI C comment */ // C++ comment statement - note no double slashes at the end of the statements
There are two differences between these two styles. First, since the double slash method was borrowed from C++, it is not ANSI C compatible and it may or may not work with some C compilers. (It will work with the MS QuickC compiler used in the lab.) Second, the C++ version does not require an ending comment statement; it instead assumes that everything to the right of the statement is a comment and it assumes that the line following the comment statement is not a comment.
22
3. Introduction To C / 3.2.
/* incl. file */ /* include file /* include file */ /* include file with graphics info /* see example directly below */ */
Here is a very simple example which illustrates the purpose of include files. Assume that we were to write programs which depend on predefined physical constants, such as h, c, G, k etc. Instead of looking the values up and entering them directly into each of our programs, we could write a short text (i.e. ASCII) file using the QuickC editor. Something like this:
/* SI Constants /* File Name: myconst.h #define c 3.0e8 #define G 6.67e-11 #define k 1.38e23 */ */ /* this statement defines the constant 'c' */ /* see section I. c) for more information on 'define' statements. */ /* this defines the constant 'G' */ /* this defines the constant 'k' */
Next, we save this file in the include subdirectory as "myconst.h" (Note, the extension ".h" is used to distinguish include files from C programs which have extension .c). If we enter in any of our future programs: #include <myconst.h> then all the above defined constants can be used directly in such a program and they do not have to be declared or entered again. In other words, when your program compiles, the entire content of 'myconst.h' is directly pasted or 'included' in the program. The most commonly used include files contain function declarations for predefined C functions. Each C function must be declared somewhere, i.e. the function's name, its type and the arguments must be declared before it can be used. In our programs, we will be using numerous predefined C functions such as sin(), printf(), scanf() etc. Since these functions must be declared we should type their often lengthy and cumbersome declarations in our programs. Lucky for us, someone has already entered all predefined function declarations in various include files. Hence, using an include statement in our program which refers to a particular include file is identical to typing all the statements in the include file directly into our program. The declarations from related functions are usually grouped together. For example, the declarations for all predefined math functions, such as sin(), exp(), rand(), log() etc., are stored in the file 'math.h'. To find out which include file is needed for a particular function, read the summary help screen (i.e. move the cursor to the function statement in your program and press f1). Note, if you forget to enter the corresponding include file for a function, the program may still compile without error messages but it may not work; hence, always check that each predefined C function has its corresponding include file declared! Final Note: No semicolon follows the include statements.
23
3. Introduction To C / 3.3.
Define Statements
Description Usage Prints formatted data to screen Display numbers and text Reads in formatted data. To read in numbers Prints character string to screen Display text Reads in a string (termin. by carriage return) To read in text Detects if any key on keyboard has been hit To stop a repeated process Reads a byte of data from a port Computer interfacing Sends a byte of data to a port Computer interfacing Creates a data buffer Arrays and data input Creates a random # between 0 and 32768 Simulations, Tests Calculates xy Calculates x
24
3. Introduction To C / 3.5.
Global Variables
Sometimes you want to use a function that is not included in the library of predefined functions. If this is the case then you will have to create your own function. Here is an example: you need to convert Kelvin to Fahrenheit. As a first alternative, you could simply enter the code for the conversion into your main program. The code for the conversion will simply become part of the main code. This approach is fine if you need to use the Kelvin Fahrenheit conversion in your program only once or twice. On the other hand, if you need the conversion often and at different places in your program, you are better off to write a function which, when it is called, will return the converted value. In general, it is a good idea to package your code in functions. The decision when to write your own function and when to keep the code in 'main' depends on how often the function will be called and from how many different places in the program. Writing your own functions makes your program much more readable and 'debuggable'. In addition, if any of your functions turn out to be useful then they can be 'recycled' in other programs and over time you will build-up your own library of functions. Without going into the details of the function syntax, (see section 3.7) it is important to understand that every C function that you intend to use in your program must be declared before it can be called. If you use predefined functions, then the function declarations have already been stored in include files; hence, at the beginning of the program, you must specify these include files (see section 3.2). If you write your own functions, place the function declarations before 'main'. For the exact syntax of the function declarations, see section 3.7 where we cover functions in more detail.
3.6. main( )
Every C program needs a 'main' function. This is the place where the main body of your code is placed. A successful C program executes all the statements placed in main. Functions are only executed if they are called from within 'main'. Main is the only C function that doesn't need to be declared.
3.7. Functions
A function consists of three elements: a) Function Declaration b) Function Definition c) Function Calls. First let's see how you would use a predefined function. Below is a complete program using the 'printf' function.
/* Simple Stoneage Program */ #include <stdio.h> main( ) {
25
3. Introduction To C / 3.7.
Functions
printf( "Yabadabadoooo!!!!" ); }
/* function call */
As you probably guessed, when you execute this program it will simply write "Yabadabadooo!!!!" (without the quotation marks) across your screen. Not very impressive but, nevertheless, a beginning. We compare this program now with the three elements that a function consists of. The first element, the function declaration for 'printf', has already been declared (by someone else) in the include file 'stdio.h'. Instead of specifically declaring the 'printf' function we simply paste the entire 'stdio.h' file into the program by using the include statement. Not only does this free us from having to type in the exact and very cumbersome function declaration for 'printf', the 'stdio.h' file also contains many other useful function declarations of other frequently used functions. The second element, the function definition, is missing entirely. Because we are using a predefined function, someone else has already defined it for us. There is no need for us to enter the 'printf' function definition again. The third element, the function call, is executed by placing an argument, in this case "Yabadabadoooo!!!!", into the 'printf' function. That's really all there is to using predefined functions. First you declare them by pasting the appropriate include file into your program and then you call the function with suitable arguments.
If you need to create your own function then you will need to use all three function elements. a) To declare a function, use the following syntax: type Name(type arg1, type arg2,....);
/* Examples: Function Declarations short Factorial( short x); float Binomial_Dist( short N, short n, float prob_true); float Fahrenheit( float Kelvin );
*/
Function declarations 'tell' the compiler the number and type of arguments to expect and the function type. Similar to variables, functions are always of a specific type because they return a value of that type. Always declare the functions before main. It is a good idea to capitalize the first letter of a function name to distinguish it from a variable name. b) To define (i.e. write) a function, use: type Name(type arg1, type arg2,....) { statements; return (value); }
26
3. Introduction To C / 3.7.
Functions
/* Examples: Function Definitions float Fahrenheit( float Kelvin ) { float temp; temp = 1.8 *( Kelvin - 273) + 32.0; return( temp ); } void Clear_Screen( void ) { _clrscreen(); return; }
*/
*/ */
Note, while the function declaration statement is followed by a semicolon, the function definition statement is not followed by a semicolon. (Confusing? Try to remember that main is a function (definition) and it is not followed by semicolon.) c) Finally, to call a function use:
Name(arg1, arg2,....);
/* Example: Calling Function Fahrenheit /* Note: This is a complete and working C program #include <stdio.h> float Fahrenheit( float Kelvin ); main( ) { float a = 20; float temp_F; temp_F = Fahrenheit(a); printf("Temp is: %f\n",temp_F); } /* needed for printf() statement /* Function Declaration
*/ */ */ */
/* local variables
*/
*/ */
float Fahrenheit( float Kelvin ) { float temp; temp = 1.8 *( Kelvin - 273) + 32.0; return( temp ); }
/* Function Definition
*/
/* conversion
*/
27
3. Introduction To C / 3.7.
Functions
As you can see, when you call a function you must not specify the type of the function or the type of the function arguments; you have already taken care of that in your declaration and definition statements. Here are a few fundamental facts about functions. Though a function may have any number of arguments, it can return at most only one item; the returned item can be a character, an integer, a floating point number or a pointer. Hence, you state in the function declaration what you expect the function to return. If your function returns nothing, which is perfectly fine, then declare it type 'void' and simply use 'return;' and omit the parentheses in the return statement.
void Print_it( char *it) { printf( it ); return; } /*The function definition; note, the function is not */ /* expected to return anything. */ /* The function returns nothing. /* Note the omission of the parentheses. */ */
Function arguments do not have to be of the same type. Finally, do not declare the function arguments again in your function definition.
float Joules( float ev) { return( ev / 1.6e-19 ); } float Joules( float ev) { float ev; return( ev / 1.6e-19 ); } /* Example CORRECT: */
*/ */
For some additional examples about functions see chapter 7, Loop Instructions.
28
4. Variables / 4.1.
4. VARIABLES
*/
In older versions of C, variables of type 'short' are also sometimes referred to as 'int'.
*/ */
Inside a function, you may declare a variable only once. If you want to change the type of a variable you may assign a temporary new type to the variable through 'type-casting.' To type-cast a variable precede the variable with the new type enclosed in parentheses. For example, in the above program, variable x was declared type 'short' (i.e. an integer); using an expression such as: a = (float) x/3 will change the variable x for this equation temporarily to type 'float' (i.e. a real number). Type casting is used often in mathematical expressions and in function calls.
29
4. Variables / 4.3.
Scope of Variables
*/ */ */ */ */
short Second_function( short value) { short k = 5; /* 'k' is local to Second_function return( value * k ); }
*/
Now consider writing a very short and simple program; all your code would be placed in 'main' and you would not write your own functions. The question is should you declare your variables as global (Example 1) or as local variables (Example 2)?
/* Example 1: x is global */ short x; main() { x = 21234/2; }
30
4. Variables / 4.3.
Scope of Variables
As a matter of fact, both Example 1 and Example 2 would work and for simple programs it doesn't really matter. Nevertheless, Example 2 is better because you should always keep global variables to a minimum. This is particularly important if you write large programs which contain include files and your own functions. Here are more complicated examples using global and local variables.
/* Example 3: */ void My_Function( void ); short temp; main() { temp = 412; printf("%d", temp); My_Function(); printf("%d", temp); } void My_Function( void ) { temp = 3; return( void ); } /* Function Declaration /* temp is global */ */
*/ */ */
/* Example 4: */ void My_Function( void ); main() { short temp; temp = 412; printf("%d", temp); My_Function(); printf("%d", temp); } void My_Function( void ) { short temp; temp = 3; return( void ); } /* Function Declaration */
*/ */ */ */
Note, the only difference between the two examples is, 'temp' is a global variable in Example 3 and a local variable in Example 4. After running both examples, the printout would be: ('printf' prints the value of the variable following the comma)
31
4. Variables / 4.4.
Explanation: In the first program, 'temp' is global and both main and My_Function can "see" 'temp'. Changing the value of 'temp' anywhere in the program affects the value of 'temp' everywhere else. On the other hand, in the second program, 'temp' is local. Though the variable name 'temp' is being used in main and in My_Function, 'temp' is now two distinct variables, one local to main and the other local to My_Function. Since local variables can not "see" local variables belonging to other functions, changing the value of 'temp' in My_Function will not affect the value of 'temp' in another function. When you write very long programs you often will run out of short and easy to type variable names that are distinct. If all your variables were global then you have to worry how assigning a value to a variable might affect your entire program somewhere else. (A worry every BASIC programmer is familiar with.) With local variables, a change in a variable will affect only that variable within that function. Furthermore, if you want to reuse any of your own functions in future programs it is nice to know that they can be called directly from your program without having to declare all kinds of global variables.
This is not very readable because the output from each printf( ) function call starts right where the previous one ended. The readability can be improved greatly by formatting the output. This is done using, what is referred to in C as "escape sequences", i.e. a backslash ( \ ) followed by a letter.
32
4. Variables / 4.4.
While the complete list of escape sequences is given in Table 3 at the end of this booklet, here we will concentrate on the two most commonly used ones: the newline '\n' and the horizontal tab '\t'. Here is a program that prints the first few lines of a work by Chaucer.
#include <stdio.h> /* function declaration for 'printf" function */ main( ) { printf("\tWhan that Aprill "); /* function calls */ printf("with his shoures soote\nThe droghte of March hath perced to the roote,\n"); printf("And bathed every veyne in swich licour\nOf which vertu eng"); printf("endered is the flour;\n"); }
Note how the escape sequence characters can be inserted anywhere within a string. Here is the output from the program:
Whan that Aprill with The droghte of March hath And bathed every veyne in Of which vertu engendered his shoures soote perced to the roote, swich licour is the flour;
In addition to printing strings, 'printf( )' can also print numerical values assigned to a variable. To do so, a percentage sign followed by a type specifier is inserted into a string and the name of the variable is listed at the end of the string. When the function is executed, the percentage sign will be replaced by the numerical value assigned to the variable. The syntax for a typical printf( ) function call, for printing both strings and a variable is: printf( "string%type specifier string", variable name); where one or both 'string's' may be omitted. The type specifier depends on the type of variable used; for a sensible printout, the variable type and the type specifier must match. Not matching them correctly is a very common mistake! A more complete table of type specifiers is given in table 3 at the end. Here are the most common ones: /* Variable Type Type Specifier char c short (signed) d unsigned short u long (signed) ld unsigned long lu float f float e double lf (or le) character array (string) s x Comments
Decimal Notation (ex. 123.456) Scientific Notation (ex. 1.2345e02) Prints a variable in hex. notation. */
Though ultimately you will remember most of these type specifiers (or know where to look them up) there is a short a cut. Remember only one or two of the most common ones (probably 'e' for scientific notation for a variable type 'float') and then type cast (see section 4.2) all your variables to that type. Study the program below and its output carefully:
33
4. Variables / 4.4.
#include <stdio.h> main( ) { short i = -1; long k = 10000000; printf("Type 'short' variable: i = %d\n", i); printf("Wrong type specifier (i is signed short): i = %u\n", i); printf("Multiple Statement: i = %d\tk = %ld\n", i, k); printf("Type Casting (k to float) k = %e\n", (float)k); printf("%x\n", k ); }
/* First printf call /* Second prinft call /* Third printf call /* Fourth printf call /* Fifth printf call
*/ */ */ */ */.
Let's look at each function call in more detail. Compare the first and the second 'printf( )' function call. Though they differ in their string, they both attempt to print the value assigned to the variable 'i' which is of type '(signed) short'. The second function call prints an incorrect value because the type specifier used refers to a variable of type unsigned short. Though the variable and its type specifier do not match, no error or warning will be generated by the C compiler! This can be very frustrating especially when one tries to print out results of lengthy calculations because one will automatically assume that an incorrect value was assigned to the variable; this in turn will lead to searching for errors in the program where there are none. So far we have only printed one variable per function call. The third function call shows how to print multiple variables within a single string. In such a case, the value of the first variable replaces the first percentage sign and the second variable is assigned to the second percentage sign and so on. Also note the tab escape sequence that was used to improve the readability. The fourth function call shows how to use type casting. Though 'k' is type long, it is converted temporary and printed as a type 'float" using scientific notation. In the last call, the numerical value of k is printed in hexadecimal notation.
34
4. Variables / 4.4.
#include <stdio.h> main() { short i; /* declare variable 'i' printf("Enter a number of type 'short': "); scanf("%d", & i); printf("Value entered: %d\n", i ); }
*/
Its output will depend on the value entered but it should look something like this:
Enter a number of type 'short': Value entered: 55 55
Here are some common pitfalls of the 'scanf( )' function. Notice that an ampersand sign (&) is required before the variable name; this tells the compiler not to look at the actual variable but rather its address, i.e. it converts the variable to a pointer. (Don't worry if you don't understand addresses and pointers; they will be covered later in chapter 9.) Nevertheless, the 'scanf( )' function will not work if you should forget the ampersand! To ensure that your 'scanf( )' function works properly, it is a good idea to "echo" each time the value entered by using a 'printf( )' statement immediately after the 'scanf( )' function. Prompts are instructions informing the user what to do next. In order to be useful, they should be as specific as possible. Avoid vague statements such as "Enter a number" or "Hit any key." (The second statement has resulted in hundreds of hours of lost productivity because people spend that time searching in vain for the "ANY" key on their keyboard.) Instead use specific statements such as: "Enter the temperature in degrees Celsius: (use an integer)." Such prompts are extremely important when using a 'scanf( )' statement. If no prompt is given the user will have no clue when the computer reaches a 'scanf( )' function call. Unfortunately, the 'scanf( )' function does not allow for prompts to be included in the function arguments. Unlike 'printf( )', in 'scanf( )' you may not combine prompt strings with the format specifier! For example, the following statement will not work:
scanf("Enter a number: %d", &i); /* WRONG!!!*/
Therefore, if you want to inform the user what data she is expected to enter, then precede your scanf statement with a 'printf( )' statement!
35
4. Variables / 4.4.
To read an entire string of characters, the 'gets( )' function is used. Since we have not yet learned how to store strings in variables we will postpone the discussion of the 'gets( )' function till chapter 9.
36
5. C Operators / 5.1.
Arithmetic Operators
5. C
OPERATORS
C evaluates mathematical expressions according to precedence of the operator (i.e. multiplications and divisions are done before additions and subtractions) and the expression is executed from left to right. A table of operator precedence is given in Table 2, chapter 14, but whenever there is any doubt use parentheses. If you are not careful C arithmetic can be tricky. Note that whenever an operation is performed with variables of different types, the variable with the lower precision is automatically converted to the type of the higher precision.
main{} { short x = 1; short y = 3; float b; float a = 3.0; b = x/a; }
Hence, in this case 'b' will be 0.33333 Now consider changing the last line in the program above to: b = x/y; Though we still divide 1 by 3, 'b' takes on the value of 0.000000. Explanation: In the first case, a variable of type short (x) was divided by a variable of type float (a); hence, the result was a value of precision 'float' which was then assigned to a variable of type float (b). In the second case, both variables were of type 'short' (x and y) and, hence, the precision of the resulting division is only of type 'short' i.e. 0; assigning the result to a variable type 'float' can not recover the lost precision. Hence, be very careful when performing multiplications and divisions with integer-type variables; use type casting when in doubt! For example, changing the last line to: b = (float) x/y; results again in b = 0.33333 because 'x' is temporarily changed to type 'float'. The same arguments also hold true when an operation is performed on a variable and a constant. For example, b = x/3; results in b = 0.00000, while b = x/3.0; results in b = 0.333333
37
5. C Operators / 5.2.
Relational Operators
8 Note no power operator exists in C (i.e. if you want to calculate 2 , 2^8 or 2**8 is meaningless in C, you need to use the pow(2,8) function). C includes some shorthand notations for arithmetic operations. For example, to increment the variable 'x' by one, you could write: x = x + 1; The same statement can also be written in C as: ++x; or: x++; (++x means that x is to be incremented directly before the statement is to be executed and x++ increments x directly after; except for a few rare cases the distinction between the two cases is not important). The statements x = x+1 and x++ are identical; nevertheless, the second one is easier to read. Increment x = x + 1; ++x; Decrement x = x - 1; --x;
If you want to increment (decrement, multiply, divide) a variable by a constant value, you can use the following notation: x = x + 5; x += 5; x = x - 5; x -= 5; x = 5 * x; x * = 5; x = x / 5; x /= 5;
Logical operators are often used in conjunction with relational operators. A common example is to check whether a value falls in a certain range, such as: a<x<b Since relational operators in C can operate on only two values at one time, the above expression has to
38
5. C Operators / 5.4.
be split up into: (a < x) && (x < b) If your knowledge of logical operators is sufficient and if you possess a sense for warped jokes, you should have no problem understanding the significance of the following question: (2B) || ! (2B).
would result in 'x' being 0 if both 'a' and 'b' are 0. If 'a' or 'b' are non-zero, 'x' will be 1. In effect, these operators reduce each variable to a single bit on which they then perform their logical operation. For the vast majority of logical operations, this is all that is needed. In addition to these logical operators, C provides us with a second type of logical operators: the bitwise logical operators. Instead of reducing variables to single bits on which the operations are performed, bitwise operators perform their operation on each bit individually and then return all these bits. Here is a list of bitwise logical operators: & ^ | Let's look at an example:
a = 5; b = 10; x = a && b; y = a & b;
The logical AND in the third line results in 'x' having a value of 1 assigned; on the other hand, the bitwise logical AND operation in the fourth line results in 'y' being 0. Here are the reasons why these two operations yield different answers. In the third line, the logical AND is executed on the variables as a whole: since 'a' is not 0, it is true; the same holds for 'b'; finally, since both 'a' and 'b' are true, 'x' is also true. Therefore, x = 1. The AND operation in the forth line is bitwise. In binary notation 'a' is 0101, while 'b' is 1010. Each bit will be compared and stored individually in 'y'. Beginning with the least significant bits, 1 AND 0 results in 0. This is stored in the least significant bit of 'y'. The operator then goes on to compare the second least significant bit (see below). a: 0 1 0 1 b: 1 0 1 0 y = a&b 0 0 0 0 This process is repeated for each pair of bits and as you can see, it results in 'y' being 0. Bitwise operations are often used to "mask" certain bits, i.e. for testing a specific bit within a word. For example, if you need to check if the third bit in the variable 'z' is true, you would use the following (incomplete) statement:
if( z & 0x4 )
39
5. C Operators / 5.5.
Shift Operators
The truth table below shows that except for the third bit, the other bits are all masked off. ('X' stands for "don't care.") z: X Y X X 4: 0 1 0 0 y = z&4 0 Y 0 0
each bit in the variable 'x' is shifted two bit positions to the left; the two right most bits are set to zero; the two leftmost bits are discarded. For example, 3 << 1 equals 6.
40
6. BRANCHING INSTRUCTIONS
if( xxxx ) { statements; } if( xxxxx ) { statements; } else { statements; } /* xxxx stands for some test using relational /* operators; for example, a >= b, etc. /* the statements will be executed if above /* test was true. */ */ */ */
*/
*/
if( xxxx1) { statements; } else if( xxxx2) { statements; } else if( xxxx3 ) { statements; } else { statements; }
*/
*/
/* executed only if xxxx3 is true */ /* additional 'if else' blocks can be used if desired*/ /* executed if none of the above is true */
variable = ( xxxx ) ? variable1 : variable2; /* if xxxx is true than variable = variable1 if xxxx is false then variable = variable2 */
41
6. Branching Instructions / 5.5. Shift Operators A word about the placement of opening braces { and closing braces }: Braces in if-statements and in loop-statements are optional if they enclose one, and only one, statement; otherwise they are mandatory. Nevertheless, even when these braces are optional, it is a good idea to use them (especially, if you are new to C programming) and we will follow this convention throughout the rest of this paper. Also note that every statement between braces must always end with a semicolon!
/* EXAMPLES using a single statement between braces */ if(x == 2) { printf("x = 2"); } else { printf("x != 2"); } if( x == 2) printf("x = 2"); else printf("x != 2"); /* correct way */
/* also correct
*/
An other branching instruction, which is often used in LabWindows programs, is the switchconstruction. At each case statement, the switch variable (in the example shown below x) is compared with a constant. If the variable and the constants are identical, the statements immediately following the case statement will be executed until a break instruction is encountered. If none of constants are identical to the switch variable, then the statements following the default instruction will be executed.
/* EXAMPLE using a "switch" construction */ switch (x) { case CONSTANT1: statement1; break; case CONSTANT2: case CONSTANT3: statement3; break; default: default_statement; break; } // Variable x // if x == CONSTANT1 then statement 1 is executed
You should always keep in mind that the switch construction only works by comparing a switch variable with a constant. Unlike an if-statement, you can never use the switch construction to compare two variables! So far, all our branching instructions were based on conditions. C also has an unconditional branching instruction 'goto label', where label is any name. Keep the use of 'goto' statements to a minimum because they can obscure your code. Here is a program segment using a goto statement:
42
/* Example: Simply Menu Selection Using a Goto Statement to Repeat Process main() { unsigned short x; AGAIN: /* Label for goto statement printf("Enter 1 to read in data, 2 to display, anything else to exit program"); scanf("%u", &x) /* read in menu selection if(x == 1) /* if 1 was selected call function GetData() GetData(); else if(x == 2) /* if 2 was selected call function DisplayData() DisplayData(); else /* exit program if neither 1 or 2 was selected exit(); goto AGAIN; /* repeat menu selection; go back to Label AGAIN }
*/
*/ */ */ */ */ */
43
FOR Loops
7. LOOP INSTRUCTIONS
One very common mistake with FOR loops is forgetting to declare the counting variable. Here is a working program that will calculate the Poisson distribution. By now, you should be able to understand this program entirely.
#include <math.h> #include <stdio.h> double Factorial( double x ); double Poisson( double x, double mean); main() { double x, mean; printf("Enter Mean For Poisson Distribution: "); scanf( "%lf", &mean ); printf("Enter x: "); scanf( "%lf", &x ); printf("P(x): %lf \n", Poisson( x, mean ) ); } double Poisson( double x, double mean) in Reif Stat. Physics */ { /* P(x) = /* see page 42 mx e-m x! */
return( pow( mean, x ) * exp(-mean) / Factorial(x) ); } double Factorial( double n ) /* calculate n factorial (n! = 1*2*3*...(n-1)*n) */
44
*/
*/
*/
A very common usage of the WHILE loop is in conjunction with the kbhit() function. The kbhit() function returns 0 (False) when no key has been pressed and 1 (True) when any key on the keyboard has been pressed. This is very useful if you have a program that executes a repetitious task (i.e. if you monitor something) and you want to terminate the task. The following program segment will execute the statements in the WHILE loop until any key was pressed.
while( ! kbhit() ) /* Note Negation !, i.e. while NOT kbhit() */ { printf("%d\n", (short) inp(0x310); /* prints values read in from Port HEX 310 }
*/
The difference between a WHILE and a DO loop is: in a WHILE loop, the conditional statements are checked before a loop cycle is executed; in a DO loop, the conditional statements are tested after the execution of each loop cycle. Hence, a DO loop executes its statement(s) at least once and as many times as the conditional test remains true. On the other hand, if the conditional test in a WHILE loop is false the very first time, the statement(s) in the WHILE loop will never be executed.
45
Nested Loops
As you can see from the printout, the innermost loop is always executed first. When all the innermost loop cycles have been completed, the loop enclosing the innermost loop executes one cycle. Afterwards, the innermost loop cycles are executed all over again. Control is then transferred to the outer loop cycle and it is executed just once. The entire process continues until the condition test in the outermost loop cycle is no longer true. If this seems confusing to you, try to think of nested loops in terms of n-dimensional spaces composed of discrete blocks. For example, the above nesting consists of two loops; hence, consider a two dimensional space made of discrete elements, such as a tiled floor. When the nested loop is executed then first all the elements in the first row will be selected (i.e. row = 1). Next all the elements in the second row will be chosen and finally the elements in the third and fourth row are selected. If the above example had three nested loops, instead of the two shown, then you could imagine a cube made of building blocks; while the two innermost loops would select a whole layer of elements, the outermost would select a particular layer.
46
8. Arrays / 7.3.
Nested Loops
8. ARRAYS
So far, our programs have been very limited because we could assign only one value to one specific variable. What we would like to do is to assign values to a whole group of variables and manipulate the entire group of data. For example, if you take 6000 readings in an experiment and you want to calculate their average, it would be ridiculous if you had to type in your C program a statement such as: 'average = (x1 + x2 + x3 + x4 + x5 + + etc )/6000'. Therefore, let's look at the problem from a mathematical viewpoint and express it using a mathematical equation:
x=
1 n xk n k =1
This expression is much simpler to understand than our first expression with the 6000 terms because it uses a summation sign and an indexed variable, xk. We have already learned how to implement a summation in C, namely by using a loop statement, but we have not yet learned how to express something like an indexed variable in C. Hence, we need to learn about 'arrays' which are the computerequivalent of indexed variables. An array is a collection of variables which are all of the same type. For example the declaration:
float mydata[ 5000 ];
sets aside sufficient memory for 5000 members of an array called 'mydata'. Each member of the array is type 'float'. Each individual member of the array can be accessed by using its corresponding index, also known as offset. For example if you like to print out the value that is stored in the 457th member of array 'mydata', you would type:
printf("%f", mydata[ 457 ]);
Though we used a numerical value for the offset, we could just as well have used a variable or even an element of another array. If you use a variable for the offset it must always be of type unsigned short! (Think, it would make no sense to refer to a fractional or floating point member, such as the 2.4344th member.) An array, just like a variable or function must be declared first. It can be made 'global' by declaring it outside of main. Be careful if you decide to declare your array 'local' inside of a function or main. Remember that all local variables are created on the stack when the function is called, and destroyed again when the function is exited. While this is no problem with ordinary variables, arrays can quickly gobble up large amounts of memory (above array 'float mydata[5000]' would use 20000 bytes) and the stack has only about 2000 - 4000 bytes of memory. Hence, if you must use a large array either make it 'global' or precede the local declaration with the static statement (i.e. use: static float mydata[5000]; ). The declaration must always specify the type of the array, the array name and the number of its members. You can declare the array size with a numerical value such as: short little[ 33 ], or with a constant that has been previously defined; you can never use a variable in the array declaration statement. The following examples are legal:
short little[ 3 ]; float mydata[ 6000 ]; #define MAXELEMENT 92 long mass[ MAXELEMENT ]; /* MAXELEMENT is a constant */
47
8. Arrays / 7.3.
Nested Loops
C arrays always begin with the zeroth member and the array contains as many members as specified in the declaration. Consider the above declaration for array little; it consists of three members, namely: 1st member: little[ 0 ] 2nd member: little[ 1 ] 3rd member: little[ 2 ] Note: Though 'short little[ 3 ]' has been declared, 'little[ 3 ]' is not a member of the array! (Warning, in your program, you could write: little[ 3 ] = 5; and it will compile error-free. The C program will store the contents of little[ 3 ] in the memory locations directly following little[ 2 ] and, without any warnings, it will overwrite any program code or what ever else it finds there. When you run your program, it will either completely 'bomb out' or return garbage, or maybe, run just fine. But don't count on it!) Now we are ready to attack our original problem, namely the calculation of the average from n data points.
#include <conio.h> #define MAXNUMBER 6000 float mydata[MAXNUMBER]; main() { short i; float average; float sum = 0; /* here are statements that read data in from an experiment and assign them to an array. /* It may look something like this: for( i = 0; i < MAXNUMBER; ++i) { mydata[ i ]= inp(0x345); /* reads data in from port 345h } for( i = 0; i < MAXNUMBER; ++i) /* now calculate the average { sum += mydata[ i ]; /* calculate the sum of all values } average = sum / MAXNUMBER; /* divide the sum by the number of datapts printf("The average is: %f \n", average); } */ */ /* for inp() function /* max. number of data points /* array is declared global */ */ */
*/ */ */ */
When you declare an array, its elements are not initialized. Until you assign a value to an array element, it contains whatever information that has previously 'lived' at its memory address. There are some useful short-cuts in assigning values to individual elements. Instead of declaring the array and then assigning a value to each individual member, the values can be enclosed in braces. This short-cut method becomes very powerful when dealing with the most common C arrays, character strings.
float small[3]; small[0] = 3.0; small[1] = 334.9; small[2] = 13433.8888; /* Clumsy: Declaration & Assignment separate */
48
8. Arrays / 7.3.
Nested Loops
/* Character String Arrays char name[40]; name[0] = 'G'; name[1] = 'o'; name[2] = 'p'; name[3] = 'h'; name[4] = 'e'; name[5] = 'r'; char name[40] = {'G','o','p','h','e','r'}; char name[40] = "Gopher";
/* Clumsy: Declaration & Assignment separate /* note: though 40 elements were declared we /* only use the first 6. This is ok. It is a good idea /* to 'waste' a few extra bytes in a string to allow /* for terminating characters.
*/ */ */ */ */ */
/* Better: Declaration and Assignment together /* Best: Works ONLY with character arrays
*/ */
As a final note, I briefly mention multi-dimensional arrays. Whereas in mathematics you would use a variable x , in C you would declare this variable as: ij
/* Multi Dimensional Arrays /* Program prints every value in an multi-dimensional array #include <math.h> #define DATAPOINTS 100 float temp[ DATAPOINTS ][ DATAPOINTS ]; main() { short x, y; for( y = 0; y < DATAPOINTS; ++y) /* fill array with random numbers */ { for( x = 0; x < DATAPOINTS; ++x) { temp[x][y] = (rand()%1000)/1000.0; /* function rand() creates a rand. # */ } } for( y = 0; y < DATAPOINTS; ++y) /* print the random numbers { for( x = 0; x < DATAPOINTS; ++x) { printf("Row: %d\t Column: %d\t Value: %f\n", y, x, temp[x][y]); } } } */ /* 2 dimensional array */ */ */
The output from this program would look like: (Values may be different)
Row: 0 Column: 0 Row: 0 Column: 1 Row: 0 Column: 2 Value: 0.345 Value: 0.434 Value: 0.923
etc.... until:
Row 99 Column: 98 Row 99 Column: 99 Value: 0.743 Value: 0.334
49
9. POINTERS
A pointer is only useful if it points somewhere. Above declarations merely create pointers but the pointers don't know yet where to point to. Hence, the pointers are assigned to the address of a variable to which they will point. This is done by preceding a variable name with an ampersand (&), also referred to in C as an address operator:
/* assigning a pointer the address of a variable short a = 300; char X = 'y'; somepointer = &a; */ someotherpointer = &X; */
/* Note: this pointer was declared in the above example /* This pointer was also declared in above example */
Now the pointers 'somepointer' and 'someotherpointer' contain the address at which variables 'a' and 'X' are stored. Next want to use the value that the pointer is pointing to. In other words, we want to print out the value of variables 'a' and 'X' without using them. To find the value that is stored at the location that a pointer points to, we use the indirection operator, the asterisk: * . Hence, from the above example, the expression: *somepointer is identical to: a
/* POINTER EXAMPLES */ short x; short *pointer_to_x;
50
*/
/* store value '5' at location where pointer is pointing at /* print value variable 'x' contains */
*/ */
Here is the same example but this time we use pointers. If we want to refer to an element in the array we first find, similar to the compiler, the starting address. Hence we find the address of mass[ 0 ]. We could use:
/* declare array /* declare a pointer /* assign the pointer to the beginning of the array
*/ */ */
51
9. Pointers / 9.3. Pointers, Arrays and Functions This would be perfectly fine but unnecessary. Because the beginning address of an array is referred to very often, a special pointer was invented. Without any further explanation, this special pointer for the above array would simply be: mass. Hence, the statements '&mass[ 0 ]' and 'mass' are identical. Above example can be rewritten:
short mass[ 4000 ]; short *startarray; startarray = mass; /* declare array */ /* declare pointer */ /* assign the pointer to the beginning of the array
*/
Note that the pointer 'mass' has already been declared implicitly in the array declaration statement. So far we have only found the beginning of the array. A pointer to the 876th element can be found by adding 876 to the beginning of the array pointer. This example, will print out the value of the 876th element.
short mass[ 4000 ]; /* declare array mass short *member_876th; /* declare pointer member_876 = mass + 876; /* calculate 'address' of 876th element printf("%u", *member_876); /* print value of 876th element of array mass[] */ */ */ */
Remember, in the first example it was said that to reach the 876 element, 2*876 bytes were added to the beginning address, taking into consideration that each element of the array uses two bytes. In the above example, only 876 was added to the starting address because C automatically converted it (i.e. multiplied it by 2) to account for the element size. In other words, if you increment a 'short' pointer by one, it actually increments by two bytes. If you are only accessing the 876th member of array 'mass', then the pointer approach certainly would have been a mess and a waste of time. On the other hand, if you initialize or access a large array the advantage and the power of pointers should become obvious.
/* Example: Initialize an array using pointers */ main() { short i; short mass[ 4000 ]; short *temp_ptr; /* temporary pointer temp_ptr = mass; /* temp_ptr points now to beginning of array */ */ */ */
*/
for( i = 0; i < 4000; ++i) /* initialize the array i.e. set every element to 1 { *temp_ptr = 1; /* store 1 wherever the pointer points to ++temp_ptr; /* increment pointer to point to the next element; FAST } }
52
9. Pointers / 9.3. Pointers, Arrays and Functions As the last example, we write a function that will set all elements of an array to some value. Since we want to keep the array local, we will only pass an array pointer to the function. Furthermore, we will pass the value that we want the array to be initialized to, as an argument. The function itself will operate directly on the array; hence, no value will be returned by the function and we can declare it type 'void'. For example: void Initialize(short *array_beg, short init_value); The only problem with the above function declaration is that the function would not know how many elements our array contains. Hence, we need to provide the function with this information and we pass the information as an argument. Here is our final function which you could use in any program.
void Init(short *array_beg, short elements, short init_value); main() { static short lots_of_datapoints[ 30000 ]; /* set all array elements to 273 */ Init( lots_of_datapoints, 30000, 273 ); }
*/
*/
void Init(short *array_beg, short elements, short init_value) { /* Function works only for arrays with short i; /* less than 32767 elements. for( i = 0; i < elements; ++i) /* initialize entire array { *array_beg = init_value; /* assign init_value to each element ++array_beg; /* increment pointer } }
*/ */ */ */ */
53
9. Pointers / 9.3. Pointers, Arrays and Functions Since the character strings is an array, one may manipulate it like any other array. Consider the following program:
#include <conio.h> #include <ctype.h> #include <stdio.h> #include <string.h> char st[80]; main() { short i; gets( st ); for( i = 0; i < strlen( st ); i++) { printf("%c", toupper(st[i]) ); } printf("\n"); for( i = strlen( st ); i--; i > 0) { printf("%c", st[i] ); } } /* needed for gets */ /* needed for toupper */ /* needed for strlen */
The output of the program depends on the input, but it may look something like:
This is a TEST THIS IS A TEST TSET a si sihT
There are two new string functions in this program: 'strlen( )' and 'toupper( )': 'strlen( )' returns the number of characters a string contains and 'toupper( )' converts a lower case character to upper case. See if you can understand how the program manages to print the string backwards.
*/ */ */
54
t1 = clock(); /* second 'clock( ) function call printf("t1: %f sec.\n", t1/1000.0); getch(); /* delay t2 = clock(); /* third 'clock( ) function call printf("t2: %f sec.\n", t2/1000.0); }
*/ */ */
As expected, the first function call returns 0. Consecutive calls indicate the time elapsed between the first call and any call thereafter. There are two potential problems with the 'clock( )' function. First, though the function returns the time elapsed in milliseconds, its accuracy is nowhere near that. The internal clock is only updated every 55 milliseconds. Therefore, reading the clock more than once within 55 milliseconds is meaningless. Second, though the PC returns its time from the 'clock( )' function in milliseconds, this convention is not universal. Therefore, if you want to make your program 'portable' you should always divide the value returned by 'CLOCKS_PER_SEC'; this is a constant defined in <time.h> and it ensures that the result corresponds to the number of seconds elapsed and it is independent of the type of computer used. (A common error in dividing the returned value by CLOCKS_PER_SEC is ignoring the fact that you are performing an integer division and may loose some accuracy; if this is the case, type cast either CLOCKS_PER_SEC or the returned value to a type 'float'. Note, this is why the program above divides by 1000.0 (instead of by 1000)). Though the 'time( )' function is not as "accurate" as the clock function, it returns the number of seconds elapsed since January 1, 1970 GMT. This information then can be used to extract the date. Luckily, you do not have to calculate it; various (string) functions for such conversions into various date formats exist. Here is such a program:
#include <time.h> #include <stdio.h> #include <string.h> void main() { long ltime; time( <ime ); /* Get UNIX-style time and display as number and string. */ printf( "Time in seconds since GMT 1/1/70:\t%ld\n", ltime ); printf( "UNIX time and date:\t\t\t%s", ctime( <ime ) ); /* convert string */ }
Note how the 'ctime( )' function was used to convert the result from seconds elapsed into a more useful form. The biggest problem with the 'time( )' function is that it assumes that the internal clock in your computer has been set correctly. Unfortunately, PCs are notorious for amnesia with regard to time and date. Also, the internal clock often runs at it own peculiar pace. Therefore, if you are going to rely on the
55
9. Pointers / 9.3. Pointers, Arrays and Functions time( ) function, always check first that the internal clock works and that the DOS command 'date' and 'time' displays the correct value.
56
10. STRUCTURES
Structures are similar to arrays in that they contain a collection of data. The differences between structures and arrays are: Arrays Consists of elements Elements are all of the same type Elements are referred to by index Structures Consists of members Members can be of different type Members are referred to by name
Before you can declare a structure you must first create a structure template. The syntax for the template is: struct structurename { members; } As an example, consider a structure that contains various information about a chemical element.
/* Structure Template: anyelement struct anyelement { /* Member List: Note members can be of different type char *name; /* Name of element short number; /* Atomic number of element float mass; /* Mass of element float density: /* Density of element float melttemp: /* Melting Temperature of element }; */ */ */ */ */ */ */
Note that there is a big difference between creating a structure template and declaring a structure. Creating a template does not set aside any memory for a structure; it merely informs the compiler that a structure will be declared later, the number of members that it will contain and their type. Declaring a structure will set aside the necessary amount of memory for each structure as outlined in the structure template. In the above example, 'anyelement' is the template, while 'Al', 'Pb' and 'H' are the actual structures. Once a structure has been declared you can assign values to its members. A member is referenced by the structure's and the member's name separated by a period. Here is an example:
/* Assign values to individual structure members Al.name = "Aluminum"; Al.number = 13; All.mass = 26.982; Al.density = 2.7; */
57
Al.melttemp = 933; /* short cut: declaration and assignment combined */ struct anyelement C = {"Carbon", 6 ,12.01, 2.26, 4300};
Each member of a structure can be treated just like an ordinary variable and the content of an entire structure can be directly assigned to another structure.
printf("Al melting point is: %f", Al.melttemp); /* Al.melttemp is similar to an ordinary variable */ Pb = Al /* all members of struct. Pb are assigned the values prev. assigned to the members of struct. Al */
Instead of declaring a single structure, such as Al, Pb etc., arrays of structures can be declared. Consider the following example:
struct anyelement PTable[5]; PTable[0].name = "Hydrogen"; PTable[1].name = "Helium"; PTable[2].name = "Beryllium"; PTable[0].mass = 1.0079; PTable[1].mass = 4.0026;
58
59
11. Data Files / 11.2. Files: General When you write data to a file then the choice is yours which format to use. As a rule of thumb, whenever speed and size are of no great importance, its preferred to use the ASCII format. Also, if you want to read your data into a spreadsheet, such as Excel, then you should use the ASCII format. When you read a data file then you do not have a choice on which data format to use. You must always read it using exactly the same data format it was written. If its a binary file then you must (in addition to reading it as a binary file) also use the same variable types that were used to create the file!
If the file type (i.e. t or b) is omitted, C defaults to ASCII files. A file created as an ASCII file must always be accessed as an ASCII file; similarly, a binary file can only be read reliably as a binary file. Function 'fclose()' closes a data file; it clears any buffers that were created during 'fopen()'. The syntax for the 'fclose()' function is: fclose( some_filepointer );
60
11. Data Files / 11.4. Writing to an ASCII Data File Note that both fopen() and fclose() are C functions and that they return arguments. Though you can ignore the arguments, it is usually a good idea, especially when dealing with files, to check these arguments. For example, if 'fopen()' succeeds, it returns a file pointer; if it fails, it returns a NULL pointer. Hence, it is usually a good idea to use an if-statement to check for a NULL pointer argument.
/* number of data points to be written to file /* needed for function fopen() fprintf()
*/
fptr = fopen( "test.dat", "wt"); /* open and create text file "test.dat" for writing */ for( i = 0; i < MAX; i++) { fprintf(fptr, "%d",i/MAX ); /* write data (i.e. the value of a/MAX) to file */ } fclose(fptr); /* close file */ }
61
11. Data Files / 11.5. Writing Excel Files When data is read from a file, often it can not be determined ahead of time how many data points a file contains. Hence, data is read from the file until the end of the file (eof) has been found. Function 'feof()' is used for this purpose; it returns argument True when the end of file has been found, False otherwise.
/* minimum File READ Program #include <stdio.h> main() { FILE *fptr; float in; fptr = fopen( "test.dat", "rt"); while( ! feof( fptr ) ) { fscanf(fptr, "%f", &in); fprintf(stdout, "%f\n", in); } fclose(fptr); } */ /* required for functions fscanf() and feof() */
/* declare file pointer /* variable /* open file "test.dat" for reading only /* while end of file has NOT been reached, repeat /* read in a data point /* print out data point (to screen)
*/ */ */ */ */ */
/* close file
*/
62
11. Data Files / 11.6. Writing and Reading a Binary Data File The output file test.dat can now be read into Excel. It will look like this:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1 0.995004 0.980067 0.955337 0.921061 0.877583 0.825336 0.764842 0.696707 0.62161 0.540302 0.453596 0.362358 0.267499 0.169967 0.070737 -0.0292 -0.128844 -0.227202 -0.32329 0 0.099833 0.198669 0.29552 0.389418 0.479426 0.564642 0.644218 0.717356 0.783327 0.841471 0.891207 0.932039 0.963558 0.98545 0.997495 0.999574 0.991665 0.973848 0.9463
*/
*/
*/
63
11. Data Files / 11.6. Writing and Reading a Binary Data File
fptr = fopen( filename, "wb" ); /* open file for writing as a binary file for( i = 0; i < 100; i++) /* write data to file repeatedly! fwrite( data, sizeof( short), MAX, fptr); /* Note: the same array is written repeatedly to /* the data file; it is appended each time to the file. fclose( fptr ); /* close the file while( !kbhit( ) ) /* send the data to the DAC until a key has D2A_DMA_CH0_File( filename, 409600, f ); /* been hit. getch(); }
*/ */ */ */ */ */ */
64
12. GRAPHICS
A word of caution: Computer graphics are very device dependent. This means that the functions listed below will work only on (some) IBM computers. Clearly, the graphics functions are not part of standard ANSI C. To display any picture on your computer screen, little dots on your computer screen, known as 'pixels' are turned on/off. The appearance of an image depends on two factors: resolution and grayscale capability. The resolution is dependent on how many pixels a screen or a computer can display. Clearly, the more pixels that are available, the more crisp the image. The grayscale capability is defined by the number of gray shades a given pixel can take; most newspaper pictures consist of only two gray values, black and white. The resolution and the grayscale capabilities depend on the type of video adapter installed in the computer and the type of monitor attached to the computer. At the current time, there exist about 6 major types of video adapters available for the IBM. Each of these video adapters is capable of producing video modes with varying resolutions and grayscale capabilities. (Table 4 lists the available video modes and their resolution.)
65
P ART 2: L AB W INDOWS
67
1. Introduction / 11.6.
1.
INTRODUCTION
The C-compiler that you will be using for this course is part of National Instruments LabWindows/CVI (v4.01) (LW) package. In addition to an ANSI C compiler, it contains libraries written for data analysis, instrumentation interfacing and graphical user interfaces (GUI). The reason for relying on these additional libraries is that ANSI C is very limited, particularly when programming in a Windows based environment because ANSI C does not include any graphics commands. Since most of todays software uses GUIs, we think you should become familiar programming in such an environment. On the one hand, such programs are more attractive and user-friendly. On the other hand, the simplicity and ease for the user is bought at the expense of the programmer, and what appears very trivial to a user can often be very complicated to implement from the programmers viewpoint. In other words, the complexity shifts from the user to the programmer. Therefore, you as the programmer will have to learn, in addition to ANSI C programming, a rudimentary understanding about the Windows environment. Understanding fully how the Windows environment works is far too complex and beyond the scope of this course. Luckily for you, the LW software package will remove some of the difficult tasks and even write some of the program code for you. Nevertheless, you still need to be able to read C-code and know how to modify it.
69
2.
In a character mode based operating system, such a DOS, the operating system either waits for input or executes a single task. In a typical sequence you input the name of a program, DOS loads the program and executes it; when the program needs some user input, it outputs information and then waits until the user responds and then continues.
Dos Environment
Execute
Output
program waits for it because it has requested it in its code. Furthermore, the input comes from a predetermined device, most often the keyboard. Finally, since at any given instance only one program can execute, no provisions need to be made to check if some other program has changed the environment. As you can imagine, this type of environment is fairly efficient in terms of video and memory resources required while executing a program. The standard ANSI C is ideal for such situations and contains all the functions to work well in such an environment. From a users viewpoint, there are serious disadvantages to this environment. Because it is a single task environment, in order to run an application all other applications must have been terminated. For example, you cannot work on a document while you print a draft copy of it. Whatever task is running owns the machine. Since the user input is character based, you have to type a lot, which implies that the user has to remember a lot of commands. Overall, the hardware is not used very efficiently since most of the time the computer is waiting for user input.
Figure 2-1: DOS Environment Flowchart From a programming view, there are some advantages to this type of environment: its very easy to visualize the program flow because it flows from top to bottom. Also, input to the program is received only at specific times during the program execution, namely when the
As we can see today, clearly the users based viewpoint has won and not many users want to go back to a character-based operating system. (Programmers may be a different story.)
70
3.
Windows is a multitasking operating system that uses a graphical user interface (GUI). Multiple tasks can execute at one time and all user input is received through windows. While such an environment is userfriendlier, it is more complex than the simple character mode based operating system and a discussion of the complete operating system is clearly beyond the scope of this manual. What follows is a brief overview with excerpts taken from the Microsoft Visual C++ 4 compiler help manual. Do not worry if you do not understand everything, instead try to get familiar with the key concepts presented.
71
72
4.
LABWINDOWS CONCEPTS
The following is a detailed description of the LW environment, which operates in the Windows environment. You should try understanding the concepts presented because you will use them for the rest of this course. When you get to the examples presented in this manual, it is very important that you follow them using one of the lab machines running LW.
The top most or outermost panel is called the parent panel. It can contain child panels, which in turn can contain child panels. For example, the desktop is the parent panel of all child panels.
The Windows operating system and, therefore, LW also manages child panels indirectly by managing their parents.
Do not think of panels only as rectangular objects as shown in Figure 4-1. Panels, especially child panels, come in all types of shapes and colors as shown in Figure 4-2.
Panel Names
Each panel must have a constant name associated with it. Depending on the circumstances, LW also refers to these names as "panel labels" or "panel handles" or control IDs. Since the labels are essential for the operation of the GUI, LW will automatically suggest a default name, which for most occasions is suitable and does not need changing. For example, the suggested label for the parent panel is PANEL. A binary switch (child) panel placed onto the parent panel will be labeled as PANEL_BINARYSWITCH. (Note: as a convention, constants in C are always
Figure 4-1: Parent and child panel Child panels are tied to the parent panels in the following ways: A child panel is always located on top of its parent panel. A child panel is confined (or clipped) to the client area of the parent panel. If moved outside the parent's drawing area, any portion of the child panel outside that area is not drawn. The main actions performed on the parent (hide, move, destroy, maximize, and minimize) automatically affect a child panel.
73
4. LabWindows Concepts / 4.2. User Event Handler and Callback Functions capitalized.) A GUI with all its child panels and the associated labels is shown in Figure 4-2.
74
4. LabWindows Concepts / 4.3. Control vs. Display Panels or Input vs. Output Panels
Event Handler
User Event? Yes Get panel label where user event occurred
No
What is a valid event? As you have already read, any event that will be dealt with in the callback function is a valid event. Since there are a large number of possible user events and an even larger number of combinations thereof, for the sake of simplicity we restrict the valid user events to what is called "COMMIT" events: the user clicks with the left mouse button or hits the ENTER key. In other words, while LW has the capability to handle every type of user event, we will limit ourselves throughout this course to COMMIT events because they are adequate for most GUI situations.
No
Callback function for the panel exists? Yes Callback Function Valid Event? Yes Execute Code: No
Since what you have learned is very crucial for understanding LW programming, what follows is a reiteration and a flowchart. Be sure to study them carefully. When a user event is received, the LW event handler executes the following steps: First, LW determines the name of the panel where the event occurred. Next, LW checks if a callback function has been associated with the particular panel to handle the event. If no callback function has been associated with the panel where the user event occurred, it is ignored and LW returns to the event handler and waits for the
next user event to occur. If a callback function has been associated with the panel, LW enters it and begins to process the user event. First the callback function determines if the user event was valid. A valid event is one that falls into the EVENT_COMMIT category, i.e. left click and ENTER key. Events that do not fall into the EVENT_COMMIT category are ignored and LW returns to the event handler and waits for the next user event to occur. Finally, any additional code in the callback function is executed and after doing so, LW returns to the event handler and waits for another user event.
75
4. LabWindows Concepts / 4.3. Control vs. Display Panels or Input vs. Output Panels An LED, a message or a graph panel displays the state of a system. They indicate the status of one or more variables and output that information. For example an LED panel can be used to display that the system is busy acquiring data. Generally speaking, you do not expect that the user will input information into such a display and, therefore, no valid user events should ever be created on such a display panel. From this it follows that panels which are chosen to output information do not need a callback function associated with them because you do not expect a user event to occur in these panels. As you have learned in the previous section, even if a user still creates an user event on a display panel, it will be ignored because no callback function has been associated with the panel. On the other hand, you expect the user to generate a user event on a control panel such as a switch, a command button or any other input panel. To process such an event, you need a callback function associated with that panel. Without a callback function, the user has no way to interact with the program code because all the "important" stuff is executed in the callback functions. Almost all of the C programming that you will do in this course will consist of writing code for callback functions. You may have noticed a certain vagueness in the description to what constitutes a control or a display panel. The reason for that is that most of the LW panels do not have an inherent property assigned to them which designates them a control or a display. Most of them can be used as either a display or a control or as both and how they are being used depends entirely on whether or not they have a callback function associated with them. Nevertheless, from a psychological viewpoint it is very important that you use the panels the way most users would expect them to operate. For example, LW allows you to use LED panels as controls and switches as displays. For the sake of clarity, refrain from doing so.
76
5.
LW SOFTWARE: EXAMPLE 1
77
5. LW Software: Example 1 / 5.5. UIR-Editor To add the parent panel select from the uireditor menu: Create / Panel Every GUI created in LW needs a method for termination. If you omit it, your only method to terminate your application might be the ON/OFF switch on the computer, which, needless to say, is not a very user and hardware friendly approach to programming! Figure 5-2: Empty Project Window If at a later time, you choose to unload a project and want to start with a new one, select: File / New / Project Answer yes to the confirmation message and when the Transfer Project Options windows appears, make sure all options are checked. Therefore, you must add at least one control that allows the user to terminate your application gracefully. (Note: LW will automatically add the standard Windows controls in the upper right hand corner of the parent panel such as the minimize, maximize and exit buttons. While the first two will work, the exit button does not work by default and you have to set some additional switches in LW. You will not use this exit button to terminate the program and instead always add your own as described in the following sections.) Add a control for terminating the program by inserting a command button into the parent panel. In the uir-editor select: Create / Command Button (If the Command Button choice is grayed or disabled, click on the top bar of the parent panel.) Select one of the command buttons and then drag it to your preferred location on the parent panel. (See Figure 5-4.)
5.5. UIR-Editor
The best way to start a project is to design first the GUI using the uir-editor. This can be a lot of fun because you dont have to worry (yet) too much about programming and instead have a chance to use your creative and esthetic skills. To start the uir-editor, select from the project window menu: File / New / User Interface A window like the one shown in Figure 5-3 will open.
Figure 5-4: UIR-Editor Figure 5-3: Empty User Interface Editor (uireditor) Window First, you need to create the parent panel, which will contain all your child panels. LW often displays a puzzling selection of panels for a particular subject. While their appearance is different, their functionality is almost always identical. For example, among the five choices of command buttons displayed, the functionality
78
5. LW Software: Example 1 / 5.5. UIR-Editor is identical. The only difference is that the rightmost button allows you to paste a picture of your choice onto the button. If you want to check or alter the properties of a panel, double click on it. You will now alter some of the properties of the command button that you just inserted. Double click on the "OK" button. A new panel, the Edit Command Button panel (see Figure 5-5) opens displaying various default properties that LW has already assigned to the OK button. You will now alter some of these properties: change the constant name of the panel, associate the panel with a callback function and then change the label on the button. COMMANDBUTTON1, COMMANDBUTTON2, etc. Trying to remember which command button does what can become confusing and you probably should assign them more descriptive names. For this exercise, change COMMANDBUTTON to QUITBUTTON. You also have learned that the difference between a control and a display panel is that a control panel always has a callback function associated with it. Since you want the command button to terminate the program when you click on it, it is clear that it is an input or control panel and, therefore, requires a callback function associated with it. In the Callback Function window of the Edit Command Button panel enter the name of the callback function which will be used to terminate the program. Call the function: Bye. (Note again the C-function convention, the first letter of the function is always capitalized.) The last change that you will make to the QUITBUTTON involves its appearance in the GUI. When you look in the upper right hand panel of the Edit Command Button panel you see how the QUITBUTTON will be displayed in the GUI. Since it does not make much sense to call the termination control button OK, change its appearance. Figure 5-5: UIR-Editor Properties Panel You have already learned that every panel must have a constant name. When a panel is created in the uir-editor, LW assigns it a default name. From the Edit Command Button panel, Figure 55, you see that the command button's default name is COMMANDBUTTON. The default names are usually adequate and there is no need to change them. This is particularly true if you have only a small number of panels with similar functionality. On the other hand, things can become confusing when you have multiple panels with identical functions. Under these circumstances, LW will label additional command buttons as In the Label Appearance section change the label from OK to END or something sensible. After you have made these three changes, close the Edit Command Button window by clicking on the OK button at the bottom of this window. You should see a display similar to the one shown in Figure 5-6.
79
Figure 5-7: Generating the C-Code The window shown in Figure 5-7 appears. It is a reminder to you that every parent panel must have a control that allows LW to terminate the program and clean up any windows associated with the parent panel. This is accomplished by calling the LW QuitUserInterface function. The bottom panel of the window shown in Figure 5-7 lists all callback functions associated with your GUI and asks you if you want to use any of them to call the QuitUserInterface function. Your application consists of only one callback function, namely "Bye," which has been associated with the QUITBUTTON panel. Since we want to associate this callback function with the QuitUserInterface function, click to the left of Bye in the Program Termination / Select QuitUserInterface Callbacks window until a checkmark appears as shown in Figure 5-7. Finally, when you click on the OK button at the bottom of the panel shown in Figure 5-7, the generation of the skeleton C- code begins
5.7. C-Compiler
After you have executed all the previous steps a new window opens, the C-compiler window, displaying the newly created skeleton C-code. It should look like the one shown in Figure 5-8.
80
Figure 5-8: C-Compiler with Code Figure 5-8 shows the C-code generated which will run your application. We will discuss the general structure of it later. Instead, lets try to execute the code. Before you can do so you must save various files that you have created and also add them to the project window.
First save the C-code: In the C-compiler select: File / Save As / some filename Next add the C-code and uir-file to the project and then save it all: On your desktop, find the project window (see Figure 5-2). In the project window select: Edit / Add Files to Project / All Select and add the uir-file created previously in the uir-editor and the C-code that was just generated. The project window should now look similar to Figure 5-9 and it should display the C-code file and the uir-file. Figure 5-9: Project Window with Project Files Finally, save the project itself: In the project window select: File / Save As / some filename Now you are ready to execute your first LW program. In the project window select:
81
5. LW Software: Example 1 / 5.8. Changing the Appearance of the GUI Run / Run Project You should now see the GUI that you earlier designed, something similar to what is shown in Figure 5-10. Click on the END button to terminate it. Go back to the uir-editor and click on its top bar to activate it. In the upper left-hand corner of the uir-editor, you will see the icons like the ones shown in Figure 5-11. Click on the rightmost icon, the paintbrush
Figure 5-11: UIR Editor Tools Move the mouse cursor over an object in the parent panel that you want to change the color of. You may choose the parent panel itself. When you right-click the mouse, a color palette will appear. Slowly move the mouse cursor over the color palette until you find the only esthetically acceptable color. Left click on your color choice and repeat this procedure for any other panel. Important: When you are done choosing colors, be sure to select again the second icon from the left in the uir-editor which is the doted square, see Figure 5-11. If you forget to do so, you will not be able to add more panels to the parent panel or move panels.
82
#include <cvirte.h> /* Needed if linking in external compiler; harmless otherwise */ #include <userint.h> #include "example0.h" static int panelHandle;
Program 5-1: First Program Segment The second segment, shown in Program 5-2, is the main-function of C. Ignore the arguments of the main function and the first if-statement block in main, but pay attention to the last 5 lines of code. The LW LoadPanel function loads the uir-file created in the User Interface Editor. (Important: if you ever change the name of your uir-file in a project, make sure that the file name agrees with the one displayed on this line.) The DisplayPanel function displays your GUI on the desktop. The RunUserInterface runs your GUI's event handler routine. This is probably the most important line of code because it directs all user events to the appropriate callback functions. RunUserInterface( ) executes until the QuitUserInterface( ) is called from within a callback function. Finally, if a QuitUserInterface( ) call was received, the return 0 statement terminates the program.
int main (int argc, char *argv[]) { if (InitCVIRTE (0, argv, 0) == 0) /* Needed if linking in external compiler; harmless otherwise */ return -1; /* out of memory */ if ((panelHandle = LoadPanel (0, "example0.uir", PANEL)) < 0) return -1; DisplayPanel (panelHandle); RunUserInterface (); return 0; }
Program 5-2: Main Function The last segment, shown in Program 5-3, consists of the callback function Bye. This function is being called by the RunUserInterface function when a user event occurs in the panel, which Bye is associated with, the QUITBUTTON panel. From the function arguments you can see that the callback function receives information about the panel and the type of user event generated. The function itself contains a case-switch structure that filters the events. As it stands, it only processes EVENT_COMMIT user events, i.e. either left mouse button clicks or return key hits. Any other event will be ignored and control is returned back to the RunUserInterface function, which patiently waits for the next event to occur. When an EVENT_COMMIT user event occurs, the code between the case statement and the break statement will be executed. In this particular case, the QuitUserInterface( ) function will be executed, which as previously stated, terminates the RunUserInterface( ) and ends the program.
int CVICALLBACK Bye (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: QuitUserInterface (0); break; } return 0; }
83
5. LW Software: Example 1 / 5.10. Flowchart of the C-Code When you generate the callback function code, a blank line will be inserted between the EVENT_COMMIT case statement and the break statement. It is to remind you that this is the location where you will add any code that you wish to execute when a valid user event occurs. You will see more of that in the next exercise. In this case, the QuitUserInterface( ) function has already been inserted by LW at this location. This happened when the code generator asked you a while ago (see Figure 5-7) if you wanted to specify a callback function to terminate the RunUserInterface event handler. By adding the checkmark in the window in Figure 5-7, LW automatically inserted this function for you.
84
Segment 1
Start
Segment 2
No
No
No
Segment 3
Callback Function Valid Event? Yes Execute Code: Quit User Interface No
End
5.11. Conclusion
A lot of new material has been presented. Try to remember the most important steps: First you use the uir-editor to design the GUI. Second, you generate the C-code. Finally, you (would) add code to the callback functions.
85
Click inside of the parent panel to activate it and then add a binary switch; from the uireditor menu select: Create/Binary Switch (select any one you like, they all work the same)
Figure 6-1: GUI with the Binary Switch Change some of the properties of the binary switch: Add a callback function called FunctionIncrement. Change the switch label to Increment Variable or some other descriptive comment. To make these changes remember that you first must double click on the binary switch button to open the properties window of the binary switch. It is similar to the one displayed for the command button, see Figure 5-5. Unless you feel strongly about the default label BINARYSWITCH do not bother to change it. Before generating the new C-code, be sure to save the modified uir-file. In the uir-editor, select from the menu: File / Save
86
6. Example 2: Controls and Inputs / 6.3. Adding Skeleton C-Code for the New Callback Function
87
6. Example 2: Controls and Inputs / 6.3. Adding Skeleton C-Code for the New Callback Function
break; } return 0; }
Program 6-1: Third Program Segment, Callback Function "Bye." The new code for the entire application is listed in Program 6-1. When you compare it with the code generated previously, see Figure 5-8, you will notice that the code is identical except for the new callback function "FunctionIncrement" that has been appended. During program execution, when you left-click on the binary switch in the GUI, this function will be called by the RunUserInterface function and everything between the lines case EVENT_COMMIT: and break; will be executed. As you can see from the above code, LW has inserted an empty line at that location to remind you to insert any code. The purpose of this exercise is to increment a variable each time the binary switch is operated. The Ccode for incrementing a variable named x is:
x = x + 1;
(This could also be written in C as: x++;) In the C-compiler, add this line of code (at the blank line) to the callback function FunctionIncrement. Before you can use a variable in C, it must be declared in your code. Therefore, declare "x" globally. Towards the top of the code, right after: static int panelHandle; enter:
static int x;
Save the code and see if it executes without errors. Unfortunately, you will not see much happening when you operate the binary switch because you have not specified how to display the numerical value that the variable "x" takes on. You will have to wait till the next exercise to learn how to use panels to display numerical values. For now, you will use a quick and easy solution to output a variable, namely the good, old ANSI C-function printf. To display an integer variable called x using "printf" use the following syntax:
printf("%d\n",x);
Add this line of code to the FunctionIncrement, right after the x = x+1; statement. Save the code and run the program again. While compiling the code, LW will ask you if you want to add the ansi_c.h include file to your code; answer with "yes." When you now operate the binary switch, a new window, called the standard input/output window, opens up and displays the current value of "x." Click a few more times on the binary switch and watch the value of the variable x incrementing. While this method of displaying numerical values is not as elegant as the one shown in the next exercise, it is quicker to set up, especially if you are familiar with ANSI C. It also illustrates that you still can run standard ANSI C-code in the LW environment. Therefore, if you do not know how to do something using a GUI you can always go back and do it in ANSI C. Also, you should remember this method because it can come in very handy for debugging code. Finally study the flowchart shown in Figure 6-2. Compare it with the one shown in the previous chapter and notice the new callback function. To emphasize the callback function, this flowchart has been simplified; checks for valid user events and checks for callback functions associated with panel were omitted.
88
6. Example 2: Controls and Inputs / 6.3. Adding Skeleton C-Code for the New Callback Function
Start
89
7. Example 3: Output and the SetCtrlVal Function / 7.1. Display Panels Overview
90
Figure 7-1: Library Utility for the SetCtrlVal Function General information about the function can be obtained by right-clicking anywhere in the gray area. When you right-click in one of the function argument boxes, you receive specific information about the selected argument. Now enter the appropriate values for the function arguments. Begin with the PanelHandle argument, which requires you to enter the name of the variable that stores information about the parent panel. You 91
7. Example 3: Output and the SetCtrlVal Function / 7.3. LW Library Utility could determine the name of that variable from studying the existing C-code but LW gives you an alternate method and instead lists all suitable variables so you can select the correct one. To do so, left-click in the window corresponding to the PanelHandle argument. From the library utility menu select: Code / Select Variable Click on: Show Project Variables You should now see a listing of the suitable project variables. In case you forgot, the C-compiler has previously assigned the variable panelHandle as the variable that stores the constant value associated with the parent panel. Select it by double clicking on panelHandle. Note how the new argument was added to the temporary function code in the bottom panel of the library utility window. The second function argument requires you to enter the constant name of the panel which will display the numerical value. Unless you did change the default label that LW assigned to the numerical panel, the name should be NUMERIC. You could just enter that name (with some modification as you will see in a second), but it is better if you use the library utility and let it list all the constant names. Click inside ControlID window and select the following menu choice: Code / Select UI Constant A list of all the constant labels is displayed. Double click on: PANEL_NUMERIC. Note that while the child panel was called NUMERIC, LW automatically added the PANEL suffix to it, identifying it as the child panel of the parent panel called: PANEL. It may appear somewhat confusing to keep the names of the labels straight, i.e. is it PANEL_NUMERIC or simply PANEL? So remember that when you are working with code, you should always use the complete name, i.e. parent panel and child panel. This is yet another reason why it is advantageous to use this library utility because it automatically adds the correct name. Finally, the last function argument requires you to enter the variable whose value you want to display, which is "x." Again, click in Value window and then select: Code / Select Variable Double click on the variable x. The Status argument corresponds to the return value of the function. This value usually corresponds to an error code whose properties are listed when you right click in the return argument window. Among other things, it will tell you if the function executed successful or not. Whether or not you choose to store and process the return value is up to you. Clearly it is essential when you need to debug your code. In this particular exercise, you do not have to store the returned value, i.e. we simply assume that the function will execute correctly.
92
Figure 7-2: Library Utility for the SetCtrlVal Function Completed Your library utility panel should now look like the one shown in Figure 7-2. As you can see, all appropriate arguments have been added to the function code and you are now ready to paste this line of code into your program. Before you can do so, you need to place the mouse cursor at the appropriate location in your program code where you want this line of code to be inserted. Since the purpose of this exercise is to replace the printf function from the previous exercise with the SetCtrlVal function, move your cursor to that location: Go temporarily back to the C-editor and find the printf statement. Delete it. It is no longer needed. Insert an empty line at that location. This is where the new code will be inserted. Make sure the cursor stays at the beginning of this line before you return to the library utility panel. In the library utility panel, select from the menu: Code / Insert Function Call The line of code shown at the bottom of the utility window has now been inserted in your program. Close the library utility panel by clicking on the X in the upper right hand corner of the panel. The callback function for the binary switch, FunctionIncrement, should now look similar to the one shown in Program 7-1. As you can see, the library utility inserted one new line of code, the SetCtrlVal function. Looking back, it certainly appears like a lot of complicated work has been done for inserting just one line of code. It would have been much simpler just to type the line of code into your program. This may be true in this particular case where the function is relatively simple and the choice of variables pretty straightforward. Nevertheless, once you get used to using the library utility, it can save you a lot of time and trouble, as you will see.
int CVICALLBACK FunctionIncrement (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: x = x + 1; SetCtrlVal (panelHandle, PANEL_NUMERIC, x); break; } return 0; }
93
7. Example 3: Output and the SetCtrlVal Function / 7.3. LW Library Utility Program 7-1: The Callback Function "FunctionIncrement." Save the modified code and run the program. A GUI similar to the one shown in Figure 7-3 will appear. Notice how the variable increases when you flip the binary switch.
Figure 7-3: GUI with the Binary Switch and Numerical Display.
94
8. Example 4: Input and the GetCtrlVal Function / 8.1. The GetCtrlVal Function
8.
In this exercise you will learn how read a value from a panel. You will use this method if you want to read a numerical value that was entered into a numerical panel or, as in this exercise, the status of a switch. You will also become more familiar with the library utility.
Figure 8-1: Binary Switch Properties Panel To add the GetCtrlVal function to your code, use the library utility by selecting: Library / User Interface / Controls, Graphs, Strip Charts/ General Functions / Get Control Value From the previous exercise, you should be able to determine the values for the first two arguments. Set the PanelHandle to PanelHandle. Select PANEL_BINARYSWITCH for the ControlID.
95
8. Example 4: Input and the GetCtrlVal Function / 8.2. Declaring Variables through the Library Utility
Figure 8-2: Declaring Variables through the Library Utility You have already seen (in the properties panel, Figure 8-1) that the variable containing information about the state of the binary switch is of type int. Therefore, check that in the variable declaration window, Figure 8-2, iOnOff matches this type. If it isnt already selected, make sure that the checkmark Add declartion to the top of target file has been selected. Using this option will make the variable declaration a global one. (Be sure to have the C-compiler windows open.) Click on OK. The declaration for the variable iOnOff has now been inserted at the top of your code. When you return to the library utility panel note how the LW library utility automatically inserted the address operator, &, in front of the iOnOff variable in the third function argument. It did so because the GetCtrlVal function requests a pointer to the third argument and LW automatically converted the iOnOff variable to the appropriate type for you.
96
8. Example 4: Input and the GetCtrlVal Function / 8.3. Adjusting the Code in the Callback Function Go back to the c-code editor and check if the changes made to your code by the library utility were appropriate. You should see the following line of code that has been inserted at the beginning of your program:
static int iOnOff;
This corresponds to the (global) variable declaration for iOnOff. With the GetCtrlVal function you have now a method to determine whether the binary switch ON or OFF. Nevertheless, you still need to add an if-statement if you want the variable "x" to be incremented only when the switch is in the ON position. Modify the code in the FunctionIncrement callback function as shown in Program 8-1:
int CVICALLBACK FunctionIncrement (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: GetCtrlVal (panelHandle, PANEL_BINARYSWITCH, &iOnOff); if( iOnOff) { x = x + 1; SetCtrlVal (panelHandle, PANEL_NUMERIC, x); } break; } return 0; }
Program 8-1: The Callback Function "FunctionIncrement." Save and run the program. It should now increment only when the switch is going from OFF to ON. Keep in mind that in this exercise, you read the state of a binary switch. Reading the input provided by a numerical panel would have been a similar procedure. You use the GetCtrlVal function to read the numerical value that was input and then you store that value in a variable.
97
9.
EXAMPLE 5: TIMER
In the previous examples the callback functions were always responding to user events created by the mouse or the keyboard. In this example you will make use of another type of valid user event, the clock ticks (called EVENT_TIMER_TICK) caused by an adjustable software timer that is part of LW. After you have added the timer to your GUI, it will call its callback function at preset time intervals and thereby executes any code in the callback function. As you will see, its rather easy to implement and set up a timer. In this example you will again modify the application from the previous example so that it increments the variable "x" automatically each second when the binary switch is in the ON position.
Figure 9-1: Timer Property Window While you already have the properties window open you should examine the other choices. The timer interval is already set to 1.0 second, so there is no need to change that. Also, since the timer is not displayed during program execution, there is no need to change the label of the timer. Close the property panel by clicking on the OK button. Generate the callback function code for the timer by right-clicking on the timer icon and by selecting: Generate / Control Callback 98
int CVICALLBACK FunctionClock (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_TIMER_TICK: break; } return 0; }
Program 9-1: Callback Function "FunctionClock" If you were to execute the program at this point, the application would call the FunctionClock function every second and execute the statements between case EVENT_TIMER_TICK: and break;. As you can see from the code segment in Program 9-1, no code exists between these lines so you will need to add the appropriate statements. First, you want to increment the variable "x" at each timer tick. Therefore, you need to add the statement:
x = x + 1;
immediately after the case EVENT_TIMER_TICK statement. Second, you want this statement to be executed only when the binary switch is on. Similar to the previous example you need to enclose the increment statement inside an if-statement that checks if the binary switch is in the ON position. The partial program listing, Program 9-2, should accomplish all these tasks. Study it and make the appropriate changes to your code.
int CVICALLBACK FunctionIncrement (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: GetCtrlVal (panelHandle, PANEL_BINARYSWITCH, &iOnOff); break; } return 0; } int CVICALLBACK FunctionClock (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_TIMER_TICK: if( iOnOff) { x = x + 1; SetCtrlVal (panelHandle, PANEL_NUMERIC, x); } break; } return 0; }
99
9. Example 5: Timer / 9.2. Adding Code to the Timer Callback Function Since the variable is now incremented in the timer callback function "FunctionClock," you no longer need to increment it in the callback function "FunctionIncrement." Therefore, as shown in Program 9-2, remove the increment statement and any other code directly related to it from the "FunctionIncrement" function. Save the code and execute it. Test the GUI. You should see the variable incrementing every second when the binary switch is in the ON position. Also observe, as mentioned previously, that you do not see the timer icon in the GUI while it is executing. Shown below is a flowchart of your application. You can see the how the callback functions interact with the RunUserInterface.
100
Start
No Switch On Yes
101
9. Example 5: Timer / 9.4. Time Intervals Situations can arise when you need to call one function at one time interval while you want to call another function at another time interval. For example, you might want to take data at very short time intervals but you want to display a running average of the data only every few seconds. There are two ways to deal with such a situation: you either use multiple independent timers or you use one timer and the execute the various events after a predetermined number of clock ticks. Usually it is much easier to work with multiple independent timers. In that case each timer has its own callback function and executes its code at its preset rate. This method works fine as long as it is not important if the different timers are not exactly in phase or that their phase may change during program execution due to one timer slowing down or speeding up. Sometimes you need to trigger two events at different rates but with the trigger of one event depending on the trigger of the other. For example, assume that whenever the first function has been called four times, the second function should be called once. Using two independent timers with one timer interval being four times longer that the other might work under most circumstances. Nevertheless, there is no guarantee that one of the timers could not fall behind and that you could receive sometimes five clock ticks (or three) during one interval. If that is a serious concern then add only one timer to the GUI and in the callback function count the clock ticks. Execute one of the events each time but the other only if the number of clock ticks elapsed is a multiple of four.
102
10. Example 6: Reading and Setting Panel Attributes / 10.1. Adding a Control Knob
10.
The final example teaches you how to adjust the attributes of panels. You will add a control knob to your GUI allowing you to adjust the timer interval while the program is running. Later you will learn how to change the attributes of a panel. As you have already learned, panels can be used to input or output numerical values using the GetCtrlVal and SetCtrlVal functions. In addition, each panel also has a set of attributes that can be read back and changed using the appropriate functions. Probably the most obvious attributes of panels are color, size and position. Nevertheless, it is rare that you want to change these attributes while executing code. An example of an attribute, which is often changed through code, is the possibility of dimming of a panel. If this is applied to an LED panel, it appears as if you are turning the LED on and off. You can also dim certain controls to indicate that this particular choice is not available. The two functions for reading and controlling attributes are GetCrtlAttribute and SetCtrlAttribute. You will use them to adjust the time interval of the timer while the program is executing.
103
10. Example 6: Reading and Setting Panel Attributes / 10.2. Setting the Control Attribute
Figure 10-1: GUI with the Binary Switch Generate the code for the callback function FunctionSetTimeInterval and, similar to example 4, read the knob setting using the GetCtrlVal function into a variable (that you have to declare) of type double. (If you forgot go back to example 4.) In case you are not sure if everything is working correctly you might want to add a printf statement to the code, similar to example 2, at the location where you read in the knob value. Run the application and check that, each time you change the knob settings, the new value is printed.
104
10. Example 6: Reading and Setting Panel Attributes / 10.2. Setting the Control Attribute
Figure 10-2: GUI with the Binary Switch Back in the library utility, link the attribute argument to the variable that stores the timer interval value as set by the control knob, i.e. the variable that you declared in the first part of this exercise for the GetCtrlValue. In the Attribute Value window select: Code/Select Variable Select the appropriate variable. Finally, in the C-compiler, move the mouse cursor to the callback function associated with this control knob and insert this piece of code at the appropriate place: Code/Insert Function Call Save your code and run your application. If you have problems compare your code with the one shown in Program 10-1.
static double dTimeInc; static int iOnOff; #include <ansi_c.h> #include <cvirte.h> #include <userint.h> #include "example0.h" static int panelHandle; static int x; int main (int argc, char *argv[]) { if (InitCVIRTE (0, argv, 0) == 0) /* Needed if linking in external compiler; harmless otherwise */ return -1; /* out of memory */ if ((panelHandle = LoadPanel (0, "example0.uir", PANEL)) < 0) return -1; DisplayPanel (panelHandle); RunUserInterface (); return 0; } int CVICALLBACK Bye (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: QuitUserInterface (0); break;
105
10. Example 6: Reading and Setting Panel Attributes / 10.2. Setting the Control Attribute
} return 0; } int CVICALLBACK FunctionIncrement (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: GetCtrlVal (panelHandle, PANEL_BINARYSWITCH, &iOnOff); break; } return 0; } int CVICALLBACK FunctionClock (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_TIMER_TICK: if( iOnOff) { x = x + 1; SetCtrlVal (panelHandle, PANEL_NUMERIC, x); } break; } return 0; } int CVICALLBACK FunctionSetTimeInterval (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: GetCtrlVal (panelHandle, PANEL_NUMERICKNOB, &dTimeInc); SetCtrlAttribute (panelHandle, PANEL_TIMER, ATTR_INTERVAL, dTimeInc); break; } return 0; }
Program 10-1: Complete Program Listing In case you had some problems with LW, you will find a working copy of this code and the GUI in the "U:\pub\LW\Examples" directory on your U-drive. The files are called: Example6. They are write protected meaning you can read but not modify them. If you want to modify them, open the files using LW and then use the File / Save Copy As option and save them to your directory.
106
10. Example 6: Reading and Setting Panel Attributes / 10.2. Setting the Control Attribute Shown below is the flowchart for the entire application.
Start
No Switch On Yes
Increment x Display x
End
107
108
APPENDIX
Category
Buffer manipulation
Contents
Manipulate areas of memory on a character basis Test individual characters Convert numbers to strings and vice versa Manipulate directory structure and information Manipulate files
Functions
_fmemccpy _fmemicmp memccpy memicmp movedata _fmemchr _fmemmove memchr memmove swab _fmemcmp _fmemset memcmp memset
isalnum iscntrl islower isspace toascii toupper isalpha isdigit isprint isupper tolower _toupper isascii isgraph ispunct isxdigit _tolower atof ecvt ltoa atoi fcvt strtod atol fieeetomsbin strtol _atold fmsbintoieee strtoul dieeetomsbin gcvt ultoa dmsbintoieee itoa chdir getcwd _makepath _searchenv _chdrive _getdcwd mkdir _splitpath _fullpath _getdrive rmdir
Directory control
File handling
access fstat _makepath rename stat chmod _fullpath mktemp_searchenv umask chsize isatty remove setmode unlink filelength locking _splitpath clearerr fgetpos fread getchar rmtmp fclose fgets freopen gets scanf fcloseall fileno fscanf getw setbuf fdopen flushall fseek printf setvbuf feof fopen fsetpos putc tempnam ferror fprintf _fsopen putchar tmpfile fflush fputc ftell puts ungetc fgetc fputchar fwrite putw vfprintf fgetchar fputs getc rewind vprintf close dup2 open tell creat eof read umask dup lseek sopen write cgets cscanf getche inpw outp putch cprintf getch inp kbhit outpw ungetch cputs localeconv setlocale strcoll strftime strxfrm abs coshl hypot modf tanl acos cosl hypotl modfl y0, y1, yn acosl dieeetomsbin j0, j1, jn pow _y0l, _y1l, _ynl asin div _j0l, _j1l, _jnl powl asinl dmsbintoieee labs rand atan exp ldexp _rotl atanl expl ldexpl _rotr atan2 fabs ldiv sin atan2l fabsl log sinh bessel fieeetomsbin logl sinhl cabs floor log10 sinl cabsl floorl log10l sqrt ceil fmod _lrotl sqrtl ceill fmodl _lrotl srand _clear87 fmsbintoieee matherr _status87 _control87 _fpreset _matherrl tan cos frexp max tanh cosh frexpl min tanhl
I/O - Streams
Low-level I/O routines Console and port I/O routines Localization routines Math routines
109
Memory allocation
alloca _bmalloc _fheapset _heapmin _nfree _bcalloc _bmsize _fheapwalk _heapset _nheapchk _bexpand _brealloc_fmalloc _heapwalk _nheapmin _bfree calloc _fmsize hfree _nheapset _bfreeseg _expand free malloc _nheapwalk _bheapadd _fcalloc _frealloc _memavl _nmalloc _bheapchk _fexpand _freect _memmax _nmsize _bheapmin _ffree halloc _msize _nrealloc _bheapseg _fheapchk _heapadd _ncalloc realloc _bheapset _fheapmin _heapchk _nexpand stackavail _bheapwalk assert putenv srand getenv rand strerror longjmp _searchenv _strerror perror setjmp swab abort execve spawnl atexit execvpe spawnle _c_exit exit spawnlp _cexit _exit spawnlpe execl getpid spawnv execle longjmp spawnve execlp raise spawnvp execlpe setjmp spawnvpe execv signal system bsearch lfind lsearch qsort
Miscellaneous
Miscellaneous functions
Process control
String manipulation
_fstrcat _fstrncmp _fstrtok strcspn strncmp _strtime _fstrchr _fstrncpy _fstrupr _strdate strncpy strtok _fstrcmp _fstrnicmp _nstrdup strerror strnicmp strupr _fstrcpy _fstrnset sprintf _strerror strnset toascii _fstrcspn _fstrpbrk sscanf strftime strpbrk tolower _fstrdup _fstrrev strcat stricmp strrchr _tolower _fstricmp _fstrrchr strchr strlen strrev toupper _fstrlen _fstrset strcmp _strlwr strset _toupper _fstrlwr _fstrspn strcmpi strncat strspn vsprintf _fstrncat _fstrstr strcpy strncmp strstr bdos _dos_creatnew _dos_open _harderr _bios_disk dosexterr _dos_read _hardresume _bios_equiplist _dos_findfirst _dos_setblock _hardretn _bios_keybrd _dos_findnext _dos_setdate inp _bios_memsize _dos_freemem _dos_setdrive inpw _bios_printer _dos_getdate _dos_setfileattr int86 _bios_serialcom _dos_getdiskfree _dos_setftime int86x _bios_timeofday _dos_getdrive _dos_settime intdos _chain_intr _dos_getfileattr _dos_setvect intdosx _disable _dos_getftime _dos_write outp _dos_allocmem _dos_gettime _enable outpw _dos_close _dos_getvect FP_OFF segread _dos_creat _dos_keep FP_SEG asctime difftime localtime strftime tzset clock ftime mktime _strtime utime ctime gmtime _strdate time va_arg vfprintf va_end vprintf va_start vsprintf
System calls
Time Manipulate
system time
Variable-length args
110
Category
Comments
Relational
Assignment
Relational
Pointer
Conditional Miscellaneous
Addition Subtraction Multiplication Division Modulus Less than Less than or equal to Greater than Greater than or equal to Equal Not equal Assignment Addition Subtraction Multiplication Division Modulus Left shift Right shift Bitwise AND Bitwise exclusive OR Bitwise OR Increment Decrement Bitwise AND Bitwise exclusive OR Bitwise OR Left shift Right shift One's complement Logical AND Logical OR Logical NOT Address Indirection Base Conditional Function call Array element, structure or union member Pointer to structure member Type cast Size in bytes
111
Right to left
Left to right
Comma
Left to right
112
Flags
Type Prefix
Format Type
left justify prefix with sign prefix with blank modifies o,x,X, e,E,f,g,G far pointer near pointer short int long int or double signed decimal unsigned decimal integer unsigned octal integer unsigned hex integer fixed-point float scientific notation (%e or %f; whichever is shorter) single character string pointer character count
113