©KIPR, 2014
The KISS IDE is an Integrated Development Environment (IDE) for KIPR's Instructional Software System (KISS) platform, providing an editor and compilers for software development in multiple programming languages, with special purpose function libraries and simulation support for the KIPR Link Robot Controller. The KIPR Link is a complete Linux based system with color touch screen for user I/O. Its hardware provides analog and digital I/O ports, DC motor ports, servo ports, USB ports, TTL serial communication, and an HDMI port, plus an integrated accelerometer, IR send/receive, and other program accessible hardware.
This is the on-line manual for the development of C programs using the KISS IDE. The KISS IDE implements the full ANSI C specification and can target the host system, the simulator, or an attached KIPR Link. For information about the C programing language, including history and basic syntax, see the Wikipedia article C (programming language). For a more complete tutorial and guide for C Programming visit CPrograming. The landmark reference book on C, The C Programming Language (2nd Edition) by Kernighan and Ritchie must also be mentioned, since its authors are among the original developers for Unix and C. Their book is also the progenitor for much of the material in this documentation and other programming reference works. The Botball community website also has several articles about programming and a user forum where questions can be posted to the botball community. The KIPR Link Manual and the KIPR Sensors and Motors Manual available through KIPR have system specific information and program examples not included in this manual.
The primary purpose of this manual is to describe the C functions provided for the KIPR Link and their use with the KIPR Link simulator. This manual also includes a basic introduction to programming in C for those who already have experience with some other programming language or for those who need a refresher for C. To learn more about programming in C, consult one of the many books or websites that provide C references and tutorials such as the one suggested above.
The KISS IDE is used to manage software projects, especially those produced for the KIPR Link. When working on a project it is used to construct new (unsaved) project files and for editing old ones. Project files opened for editing are organized by the KISS IDE along a row of tabs. Each tab includes both the project name and file name. Clicking a file's tab activates its project and brings the file forward into the KISS IDE's editing panel. Projects that have been opened are displayed in the KISS IDE's side panel, where the active project is highlighted by graying out the names of those which are inactive.
The File menu for the KISS IDE is used for manipulating files associated with an open project or for files opened independently. It has entries for New, Open, Save, Save As, Print, Close and Quit. When a project's (.kissproj
) identification file is opened, the project's name is entered in the KISS IDE side panel list of opened projects. When a file is opened, if it is part of a project its project can optionally be opened as well. Clicking on a project name in the side panel makes it the active project and clicking on any file name within the project brings it forward into the editing panel. Files opened directly are automatically brought forward into the editing panel. The Save option for the file menu only applies to the file being edited (not its project).
The Project menu for the KISS IDE is used to manage software projects. At the outset a project consists of a project folder containing a .kissproj
file to identify the project. The project is empty until one or more project files are added to it. Project files are primarily those used in compiling a C program; namely, .c
and .h
files. Both .c
and .h
files can be directly added to a project from the KISS IDE. Any other type of file, such as data files and .txt
documentation files, can be indirectly added to the project folder and edited within the IDE. As is the case for any C program, one of the project files must include the main
function for the program. Dependencies that cross project boundaries may also be defined. The project menu includes entries for adding files to a project, compiling the project, and closing the project.
Before a project can be compiled, it must first be associated with a target system. The KISS IDE supports as target systems the host system, the KIPR Link, and the simulator. The KISS IDE can only provide target options for those systems currently visible to it, which will always include the host system and the simulator. For the KIPR Link to be visible is has to be both turned on and connected from its micro USB port to a host system USB port. The USB connection to the KIPR Link relies on a common third party serial communications driver which works for most systems, but sometimes requires additional (one-time) tweaking of the host operating system. After installing the KISS IDE, if the KISS IDE fails to find the KIPR Link (turned on and correctly connected via USB), see the KIPR Link Manual for information regarding adjustments for your specific system.
Clicking Compile or Run will transfer the active project to the target, where it is compiled. If Run was clicked, the compiled project will also be run by the target system if error free. Once compiled, the target system can rerun the project without recompiling it. Whenever a project is compiled (whether via Compile or Run) its .c
and .h
files are automatically saved. Compile errors are reported back for display in the report panel at the bottom of the KISS IDE display. If there is a syntax error reported for the program, the error report will also display the file name and line number where the error was detected.
The Edit menu provides additional capabilities for program entry/edit. These include indentation and find/replace. There are settings for auto completion, indent, and other features. Theme settings allow adjustment of typeface, font size, and coloration used for keywords, comments, and text strings.
Following a Quick C Tutorial introduction, more detailed sections are provided which describe
printf
and commenting out sections of codeThe C programming information provided is sufficient for most purposes, but a C programming reference such as those noted above should be consulted where more detail is needed or where elements of C not covered (such as functions with a variable number of parameters or function pointers) are needed to solve some programming issue.
A C program consists of one or more function definitions along with user specified data structures, and typically utilizes pre-compiled code from function libraries. To be a C program, one of the functions must be named main
.
The following is a simple C program which has a single defined function named main
and which utilizes the library function printf
. A C function returns a value whose type needs to be specified. As a C function main
is defined by first declaring its return data type followed by its name (main
) and argument list in parentheses (empty in this case), after which the program code for the function is given in curly braces ({}
). Comments are normally included to document the function's purpose and expected behavior, and are ignored in the compilation process.
/* C Programmer's Manual
Simple example */
int main()
{
printf("Hello, world!\n"); // simple example
}
Text between /*
and */
forms what is termed a bracketed comment, which can extend over multiple lines. Text that starts with //
forms a single line comment which continues only to the end of the line. Commenting program code is a good habit to form, especially considering it has no impact on program performance.
All C functions must have a return data type. While main
does not return a value to another function, it is expected to return an integer to the operating system (not relevant to this discussion), and so has return type int. In C, a function does not have to return a value, which is specified by return type void.
Another common numeric data type is double, which represents (double precision) floating point numbers used for non-integer calculation. There are additional basic (or ordinal) data types, and user defined data types derived from basic data types may also be employed.
Pre-compiled functions in the standard C library such as printf
need to be declared if used in a program. The system environment may or may not have been set up to #include the specifications for the library functions (in which case a preprocessor directive such as #include <stdio.h> may be needed to avoid compiler warnings and possible program failure). When the KISS IDE target is the KIPR Link or the simulator, the system takes care of the needed #include.
The function's name immediately follows the function's return type specification (in this case, main
). Next, in parentheses, are any arguments (or inputs) to the function. main
has none, signified by an empty set of parentheses.
The open curly-brace ({
) that follows the argument list signifies the start of the actual function code. Curly-braces are used to structure a program as blocks of code. For C, so-called "white space" consists of one or more of any combination of characters such as spaces, returns, new lines, and tabs, which is collapsed to a single generic white space character in compilation. For this reason, white space can be used for things like indentation that improve program readability. In particular, the KISS IDE editor automatically applies white space readability conventions to enhance files as they are edited.
The body of the function consists of a block consisting of a series of C statements. A C statement specifies a data structure or some specific action to be taken and is terminated with a semicolon (;
). The simple example program has a single statement,
printf("Hello, world!\n");
The action this statement specifies is calling the C library printf
function to format and print the message Hello, world!
to the project target. The '\n' directs printf
to "start a new line" (in effect ending the current line and positioning for subsequent printing to start on the next line). When printing reaches the bottom of the KIPR Link display, any additional lines printed cause the display to scroll up. Since calling the printf
function is a C statement, it has to be ended with a semicolon (;
). A common error made by beginning C programmers is omitting the semicolon that is required to end each statement.
The closing curly-brace (}
) for the main
function's block structure concludes its definition.
If the compiler issues a warning that printf
is being used with an implicit declaration, then prefacing the program code with the preprocessor directive
will ensure that the C preprocessor includes the declaration in preparing the code for compilation.
As a second example exhibiting features of C, the following program code defines a function named square
which returns the mathematical square of an integer. In this case the function does not represent a complete C program, since a function named main
for using it has not been defined yet.
int square(int n)
{
return n * n;
}
The function is declared with return type int, which means that it will return an integer value.
The function name (square
) is followed by its argument list in parentheses, where arguments are separated by commas if there are more than one (there is only one in this case). The argument for square
is specified to be an integer and is named n
. The data type for each argument is declared in the same manner as declaring the return data type of the function or the data type for a variable.
When a function declaration specifies arguments, the variable names representing the arguments are local to the function, or stated another way, they are valid only within the "scope" of the function (they only have meaning within the function's block of code). For this reason, variable names used within the scope of a function will not cause a semantic conflict should their names duplicate those local to some other function.
The "scope" for the function square
is what takes place within the block structure defining its actions (i.e., the contents within the curly braces surrounding its program statements). In this case, the function's actions consist of a single statement; namely, the return statement. The action of the return statement is to exit the function, assigning the function's return value to be what is specified by the return
statement, in this case the result of the computation n * n
.
Except where grouped by parentheses, expressions are evaluated according to a set of precedence rules associated with the various operators used within the expression. In this case, only the multiplication operator *
is employed, so operator precedence is not an issue.
For a third example, the following program code defines a function which performs a function call to the square
program.
#include <math.h>
double hypotenuse(int a, int b)
{
double h;
h = sqrt(square(a) + square(b));
return(h);
}
The argument list for this function has two arguments, each with integer data type. Additionally, its block structure includes a statement specifying a "working variable" h
. The data type for h
is given as floating point, since the C library function sqrt
returns a floating point argument. If its data type was specified to be integer, the fractional part of any floating point number assigned to it would be lost. In general, as is the case for h
, any local variables used within a program block (indicated by a set of curly braces) are specified at the beginning of the block.
The value assigned to h
is the value returned by calling the sqrt
function from the C math library and which calculates the mathematical square root of its argument. sqrt
is a built-in C function that takes a floating point number as its argument. For any KISS IDE target, #include <math.h> is needed when using functions from the math library.
The hypotenuse
function uses the square
function defined earlier. The sqrt
function is specified to have a floating point argument. So what happens if sqrt
is called with an integer argument instead of floating point? Answer: C will automatically coerce the integer to a floating point value (which uses a format involving both an exponent and a mantissa). If C's automatic coercion does the "wrong" thing, it can be bypassed by specifying how the value is to be coerced, which is accomplished by preceding it with the data type in parentheses into which it is to be converted; e.g., (double)(square(a) + square(b))
.
The hypotenuse
function concludes by returning the value of h
.
The functions square
and hypotenuse
still do not constitute a program since a main
function that uses them still needs to be defined. A complete program using these two functions follows:
/* Extended example: C Programmer's Manual */
#include <stdio.h>
#include <math.h>
int square(int n);
double hypotenuse(int a, int b);
int main()
{
printf("Hypotenuse of a 3,4 right triangle is %d\n",(int) hypotenuse(3,4)); // coerce hypotenuse to int
}
int square(int n)
{
return(n * n);
}
double hypotenuse(int a, int b)
{
double h;
h = sqrt(square(a) + square(b));
return(h);
}
Program behavior is described by main
, which calls the C library function printf
, which in turn calls the function hypotenuse
to obtain the parameter value, and hypotenuse
in turn calls the function square
in order to do its calculation. These calls result in the calculation and printing of the length (truncated to integer) of the hypotenuse of a right triangle by supplying the lengths of its two sides to the hypotenuse
function. Note that the floating point value supplied to printf
from hypotenuse
is coerced to integer data type, since otherwise printf
will print its floating point format as if it were an integer, a semantic error.
The two C statements
int square(int n);
double hypotenuse(int a, int b);
provide C "prototype" specifications for the square
and hypotenuse
functions used in the program. Similar prototypes need to be supplied for the standard C library's printf
function and the C math library's sqrt
function. The preprocessor statements
#include <stdio.h>
#include <math.h>
have the C preprocessor provide the needed prototypes (if not needed for a given KISS IDE target, the preprocessor will skip these statements; i.e., it is usually a good habit to put them in anyway). Examples of other commonly used system header files are <string.h>
, <stdlib.h>
, <stdarg.h>
, and <time.h>
.
The declaration of a function's prototype or it's actual definition needs to precede the function's first use in program code so that the compiler will be able to correctly translate the statement which calls the function. For this example, since the definitions of the square
and hypotenuse
functions follow the definition of the main
function, their prototype declarations need to be included before the definition of main
. Absent a declaration for a function, the compiler will issue an implicit declaration warning for where it first encounters the function, the results from which may be a subsequent compilation error or incorrect results when the program is executed.
This concludes the brief C tutorial.
Variables and constants are basic data objects used in a C program. More complex data structures and data types based on basic data objects and types can also be defined. Specification statements (such as the one used for the variable h
in the hypotenuse
program above) are used to define the variables the programmer wants to use in a program. A specification statement may set a variable's initial value as well as specifying its data type.
Variable names are formed from combinations of lower case letters, capital letters, the decimal digits (0-9), and the underscore character ('_'
). There are two restrictions that must be observed in forming the name:
Variables which are specified as function arguments or which are defined within program blocks are called local variables. Their scope is limited to the structure in which they are defined. Variables defined at the same level as the main
function and other functions (i.e., not inside any function's block structure) apply across all functions used by the program. These are called global variables and are used for data structures accessed by more than one function, or for function to function message passing (especially for functions operating in parallel threads).
Since functions and global variables are defined at the same level, they must have unique names. The name of a function or a global variable can be used as the name of a local variable, in which case the local use takes precedence; i.e., the scope of the local variable supercedes that of a function or global variable with the same name.
In C, a variable is declared by a specification statement that provides its data type, and optionally its initial value. Declarations for global variable are normally grouped before the definition of main
. Declarations for local variables are normally grouped at the beginning of the block structure in which they are used. The general form of a variable declaration is one of the following:
<data-type> can be a basic (ordinal) data type such as int, double, char, or a composite data type such as a pointer data type or a user-defined data type. User-defined data types are based on structures or on enumerated sets. In particular, a struct definition creates a user-defined data type struct <struct-name>, typically simplified by using typedef to provide the struct <struct-name> data type with a more digestible name. Just as struct provides a means for defining a data type based on a structure, enum provides a means for defining an enumerated data type based on an enumerated set of names.
If a variable is declared within a function, or as an argument to a function, its binding is local, meaning that the variable is available for use only within the context of that function definition. If a variable is declared outside of a function, it is a global variable and is available for use within any function employed by the program, including functions which are defined in files other than the one in which the global variable is declared. If a global variable represents a complex data structure whose definition would clutter up the main program file, it is sometimes advantageous to define it in a separate file to be included by the C preprocessor in preparing the program code for compilation.
The initial value of a local or global variable can optionally be specified in its declaration. If no initialization value is given, the value is indeterminate. When a global variable is initialized, the initialization value must be a constant. In contrast, local variables may be initialized to the value of arbitrary expressions including any global variables, function calls, function arguments, or local variables which have already been initialized.
The compilation process translates a program into machine executable form, initializing global variables within the resulting execute module as specified in global variable declarations. Local variable initialization doesn't take place until program execution enters the block in which the local variable is defined. The initialization occurs every time program execution enters the block, excepting local variables whose declaration is preceded by the word static. For a local variable declared to be static, initialization only takes place the first time the block defining it is entered. A static local variable retains the last value assigned to it whether or not program execution is in its block.
When applied at the global level, a static specification limits the scope of a global variable or subordinate function to the source file in which it is defined. This serves the purpose of allowing a pre-compiled library function to define global variables and subordinate functions whose names can be duplicated in a calling program without incurring error.A small example illustrating initialization of global and local variables follows:
int cnt=50; // global declaration of cnt as integer; initial value 50
double tmp=100.123; // global declaration of tmp as floating point; initial value 100.123
int function_example()
{
int x; // local declaration of x as integer; initial value indeterminate
double y=tmp; // local declaration of y as floating point; initial value global tmp
}
To recap, local variables are not initialized until the function containing them is executed. The initial value of global variables are part of the compiled version of the program, so global variables revert to their initialized value whenever the program is run.
For most purposes, integer constants are defined using decimal (base 10) integer format. An integer of type int is normally stored in memory as a 32-bit 2's complement integer, which allows values in the range [-231, 231-1]. Integer constants can also be defined using hexadecimal (base 16) integer format (marked by the prefix "0x
"), or octal (base 8) integer format (marked by the prefix "0
"), although both are awkward for representing negative numbers. For 32-bit two's complement integers, the following are true:
In C notation, these comparisons become
4053 == 0xFD5 == 07725
-1 == 0xffffffff == 037777777777
which also illustrates each of the three formats. Appending a "u" or "U" to the constant changes its value to the range [0, 232-1] rather than [-231, 231-1]. An "unsigned" specification is sometimes needed when data is to be copied to a variable of a different type; in particular, 8-bit bytes are treated as unsigned integers.
Present day computer memory is normally organized in 8-bit bytes, so hexadecimal pairs are commonly used to exhibit the value of bytes in memory. Earlier computer systems used 3-bit groupings which were represented using octal digits, explaining why octal representation is included as a means for representing integers in C.
The number of bits used for representing integers is system dependent, but is typically 32 bits for type int. The data type long int normally specifies a 64-bit integer, and is designated by appending "L" or "l" to the integer's representation. For most programming needs, integer type (int) constants are sufficient.
Fractions are an extension of the integers, and may be represented using a format termed scientific (mantissa, exponent) notation; i.e.,
0.<mantissa> × 10<exponent>
In addition to providing means for representing nominal fractional values, this format allows reasonable approximation of astronomically large or infinitesimally small numbers, emphasizing magnitude more so than exactness. Since the decimal point for a number such as 123.4567 is floated to the front in setting the mantissa and exponent (0.1234567×103), the term floating point representation is used.
Computer arithmetic is binary rather than decimal, so the exponent and mantissa are represented internally as base 2 values. Note that the terminating decimal fraction 0.110 when represented as a binary fraction is non-terminating (0.00011001100110011 ...2). This illustrates that even in simple cases a floating point value may be an approximation, where precision is improved by allowing more bits for the mantissa (double precision doubles the number of bits used for the representation from 32 to 64, adding extra bits for both mantissa and exponent).
A floating point constant can be defined using either ordinary decimal point representation or a form of (base 10) scientific notation; for example,
123.4567 == 12.34567E1 == 1234567e-4 == .1234567e3
Since the KIPR Link does not have floating point operations integrated into its CPU, floating point operations are handled by software, making them significantly slower than integer operations (although still fast in human terms). Hence, floating point should only be used for data that is inherently fractional.
A character constant is given by enclosing the character in single quote marks; e.g., 'K'. Characters are internally encoded in 8-bit bytes using ASCII representation (e.g., the internal ASCII representation of 'K' is the (8-bit) hex pair "4B").
A character string constant is a sequence of characters enclosed in quotation marks, e.g., "This is a character string.". C processes a character string as (1-dimensional) array delimited by the (unprintable) character constant '\0', which marks the end of the string within the array.
The character constant 'K' cannot be used interchangeably with the string constant "K" since 'K' is an 8-bit integer and "K" is a 16-bit string whose first 8-bits are the 8-bits for 'K' and the second 8-bits are the 8-bits for '\0'.
The special constant NULL
(a preprocessor macro included implicitly, or explicitly using #include <stio.h>) is provided by the C preprocessor to represent a NULL pointer. In general, a pointer represents the location (or address) of a data structure in memory. A NULL pointer is one which exists, but which points to nothing.
A pointer that hasn't been initialized has no semantic meaning, in contrast to a pointer initialized to NULL
, which points to nothing (think in terms of the empty set used in mathematics). To check if a pointer variable is pointing to data you compare its value to NULL
. As an illustration, suppose a linked list data type is defined to have elements with 2 components, the first of which provides a data value and the second of which is a pointer to the next logical element in the list; e.g., the components for an element in the list might represent a name, and a pointer to the element containing the next name in the list in alphabetical order. If the list is processed to retrieve the name data in alphabetical order, the last element in the list will need to have NULL
assigned to its pointer since there are no more names! NULL
then provides a (testable) pointer component value that identifies the last element in the list.
C supports the following data types among others:
32-bit integers are signified by the data type indicator int. They are signed integers in the (decimal) range -2,147,483,648 to +2,147,483,647.
Floating point numbers are best specified by the data type indicator double. 64-bit floating point numbers have at least 15 decimal digits of precision and range from about 10-308 to 10308.
Characters are 8-bit (unsigned) integers signified by the data type indicator char. A character's value normally represents a standard ASCII character code, most of which are printable.
A C pointer is a 32-bit number representing the address of a byte location in memory. A pointer that represents the location in memory where information is stored can be used to manipulate the data by performing calculations on the pointer, passing the pointer, or dereferencing the pointer (deference means to obtain the value stored at the location).
An array is a data structure used to store a sequence of homogeneous data elements (each element of the array must be of the same data type). Every array has a length which is determined at the time the array is declared. The location of an element in an array is given by supplying its index in brackets. For example, myarray[3]
references the fourth element in an array named myarray
where indexing starts from 0. By providing the index, data may be stored in or retrieved from the array in the same manner as for other variables. By specifying an array whose elements are (same-sized) arrays, an array with more than one dimension can be defined.
Structures are used to store non-homogenous but related sets of data. Before a structure can be specified, its struct data type must be defined. Any available data type can be used in its definition, including those that are user defined. In contrast to arrays, elements of a structure are referenced by name instead of number. For example, if a struct Triangle
data type is defined as
struct Triangle {
double sideA;
double sideB;
double sideC;
};
then struct Triangle x
defines a structure named x
of data type struct Triangle
. If side A for triangle x
is needed, it is referenced by name as x.sideA
.
Since a function can return a struct data type, structures provide a way for a function to return multiple data values. Structures can also be useful for reducing the number of arguments passed to functions. More importantly, structures provide a means for creating complex data structure representations such as directed graphs and linked lists. For these kinds of data structures, the struct elements may be dynamically allocated (see malloc
) or may be taken from an array set up for this purpose that has the same struct data type.
Enumerated sets are used to associate names with integer values, by default 0,1,2, ... according to how many entries are in the set. For example,
enum letter {F,D,C,B,A};
specifies an enumerated data type named letter
with associated names F,D,C,B,A which by default correspond to the values 0,1,2,3,4.
For a program, the declaration
enum letter grade;
specifies a variable named grade
of type enum letter
, to which can be assigned any of the names F,D,C,B,A (or equivalently the integer values 0,1,2,3,4). For example,
grade = C; // same as using grade = 2;
Enumerated sets are particularly useful for switch
constructions since cases can be referred to by names rather than values; e.g.,
switch(grade) { case A: // case action for an A grade break; case B: // case action for an B grade break; ...and so forth.
While enumeration names must be distinct, the associated values do not need to be. Note that in most instances, #define could be used in the same manner intended for enumerated sets, but enumerated sets have the advantage of the actual values being generated automatically by default. It also needs to be noted that no check is made to see if a value assigned to an enumeration variable is valid, which means the program needs to ensure the validity. An invalid value may produce an unpredictable outcome; for example, assigning a negative value to the grade
variable above may produce an invalid outcome if the variable is being used in a comparison.
A pointer is the (numeric) address of the location in memory where a data element is stored. Memory addresses begin at 0 and increase by 1 for each byte of memory. Limited arithmetic operations may be performed on pointers, but the value of the resulting pointer depends on the data type pointed to. For example, adding 1 to the pointer for a char data item increases the pointer value by 1 since that advances the pointer to the next character in memory. In contrast, adding 1 to an int pointer increases its value by 4 since that advances the pointer to the next integer in memory. Using a pointer to try to access memory not allocated to a program will probably cause a system error or crash, so it is important to insure that pointers used in a program address valid objects in memory.
A pointer data type can be defined for any allowed data type, included user-defined data types. When used as a unary operator (one argument), *
is the indirection (or "value at address") operator, and is used for defining and dereferencing pointer variables. For example,
int *p;
defines p
to be a variable whose value is a pointer to a memory location holding an integer.
When used as a unary operator, &
is the memory address operator. The address of a variable named x
is given by &x
and so if x
is an integer variable,
p=&x;
assigns the memory address of variable x
to the (integer) pointer variable p
.
Retrieving the value pointed to is known as dereferencing the pointer and is given by *p
for pointer variable p
. For the example above, both variable x
and *p
represent the same value in memory. *(p+1)
retrieves the (integer) value of the next integer in memory (which is 4 bytes further along than x
). Furthermore, the contents of the address can be changed by assigning a value to the dereferenced address; e.g.,
*(p+1) = *p + 1;
It is often useful to deal with pointers to objects, but great care must be taken to insure that the pointers used at any point in your code really do point to valid objects in memory. It is easy to get confused by what a pointer is addressing when it is taken from a complex structure with pointers to pointers or something similar.
Pointers are often used instead of global variables to provide a function with access to data external to the function. If a pointer is passed to a function as an argument, the function then has access to the memory location for the pointer, which could also represent a local variable external to the function. If the function uses the pointer to change the value in the memory location, it will also have changed the value of the external local variable.
For C, the scope of function parameters is local to the function, with values assigned to them only when the function is called. Programming languages typically employ one or more evaluation strategies for function parameters. The ones usually cited are call by value, call by reference, and call by name.
Arrays can be defined in C for any supported data type, including user defined data types. The name of an array is actually a pointer to its first element, the one whose index is 0, so passing an array to a function is a call by reference. Multi-dimensional arrays are defined as arrays of arrays (or arrays of pointers). Arrays are useful for allocating space for many instances of a given data type, arranged sequentially in memory, which provides means for iterating over the set of values in the array.
The definition for an array specifies its data type, name, and index structure, which is given inside square brackets. The following statement declares an array of ten integers:
int ex_array[10];
The elements of the array are numbered from 0 to 9. Elements are accessed by enclosing the index number within square brackets; e.g., ex_array[4]
denotes the fifth element of the array ex_array
(since counting begins at zero). Note that ex_array == &ex_array[0]
.
Arrays not initialized at declaration contain indeterminate values. Arrays may be fully or partially initialized at declaration by specifying the array elements within curly braces, separating the array elements from each other by commas. For example,
int ex_array[10]= {3, 4, 5, -8, 17, 301};
initializes the first six elements of ex_array
, with ex_array[4]
equaling 17
, ex_array[0]
equaling 3
, etc.
If no size value is specified within the square brackets when the array is declared, but initialization information is given, then the size of the array is determined by the number of initialization elements given in the declaration. If a size is specified and initialization data is given, but the length of the initialization data exceeds the specified length for the array, the excess data will be ignored (and the compiler will issue a warning).
Character strings are implemented as arrays of characters. An array of characters can be initialized character by character, but can also be initialized by using a string constant, for example:
char ex_string[]= "Hello there";
This initializes the character array ex_string
with the ASCII values of the characters in "Hello there
" terminated by the (unprintable) ASCII character '\0'. The length of the array is 12, which is the number of characters in "Hello there
" plus 1 for the '\0' string termination character. If the array length had been declared, the initialization would leave the balance of the array indeterminate if more than 12 and truncated (including '\0') if less than 12. When a character array containing a string constant is used as an argument for printf
, the format specifier %s marks where to insert the string in the formatted output; for example,
char ans[10]="no";
int q=3;
printf("The answer to %d is %s.\n",q,ans);
produces the (formatted) output "The answer to 3 is no.
".
The standard C library has string functions for assigning strings (strcpy
), determining string length (strlen
), combining strings (strcat
), and the like (look for string functions in your C reference). For example,
strcpy(s,"Example string");
is used to copy the character string in the second argument of strcpy
to the character array in the first.
To make sure that the C preprocessor includes the prototype declarations for the string functions, programs that use them normally have the C preprocessor directive
at the start of the program code. This directive, like the #include <stdio.h> directive is ignored if it is issued again elsewhere in the program.
When a character array is initialized using the curly braces syntax, unless '\0' is included, there is no string delimiter, and printing the array as a character string using printf
will produce indeterminate results. When declaring a character array that is intended to hold character strings, the array size needs to be at least 1 larger than the size of the maximum string it will be used for to allow for the '\0' string termination character. For example, given
char pg_string[81];
strings of length up to 80 can be stored in the variable pg_string
When an array is passed to a function as an argument, the address of the array's initial element is actually passed, rather than the elements of the array, a call by reference as discussed earlier. Hence there is not a local copy of the array inside the function, and any modifications the function makes to the array are to its location in memory when declared (what is local to the function is the copy of the address of the array's initial element passed as an argument).
For a function to be able to treat an argument as an array, the argument has to specify the array's data type and index structure.
As an example, the following function has arguments for an index and an array, printing the array element at the index value.
void print_element(int indx, int arr[])
{
printf("Value at index %d is %d\n", indx, arr[indx]);
}
The use of the square brackets specifies that the argument is a 1-dimensional array of integers.
Alternatively, since the array name represents a pointer to the first element in the array, the function argument could be specified as pointer variable, in which case the square brackets would be omitted. In this case, in the body of the function the pointer variable name would be used instead of the array name. For example,
void print_element(int indx, int *p)
{
printf("Value at index %d is %d\n", indx, p[indx]);
// or you could use printf("Value at index %d is %d\n", indx, *(p+indx));
}
Either of the following two example calls to the function will work, whichever of the two versions is used:
print_element(3, ex_array);
print_element(4, &ex_array[0]);
A two-dimensional array is just one-dimensional array whose elements are one-dimensional arrays. For example
int k[2][3];
specifies an array of length 2 whose objects are length 3 arrays integers. k
can be viewed as a two-dimensional array with 2 rows and 3 columns, where the first row has as elements k[0][0], k[0][1], k[0][2]
and the second the elements k[1][0], k[1][1], k[1][2]
. k[0]
and k[1]
represent rows of 3 elements each. Hence, in addition to accessing any element of the array using index bracket notation, any row in the array can be accessed similarly. Arrays with any number of dimensions can be generalized from this example by adding more brackets in the array declaration.
The initialization
int k[2][3] = {{0,1},{3,4,5}};
illustrates initialization of parts of the array, where only k[0][2]
has not been initialized. Conceptually the array is
0 1 ?
3 4 5
As noted above, structures are used to store non-homogenous but related sets of data. In order to specify a structure, a struct data type for it must first be defined. Any available data type can be used in the structure definition, including those that are user defined. The elements of a structure are referenced by name to access them.
Since a function can return a struct data type, structures also provide a way for a function to return multiple (named) data values. However, the primary purpose of structures is to provide a means for creating complex data structure representations such as directed graphs and linked lists. In the construction of this kind of data structure, structure elements may be dynamically allocated (see malloc
) or alternatively, be taken from an array having the struct data type.
The following example illustrates structure definition, creation of an array of structures, and access to its elements.
#include <string.h> // make sure the string function declarations are present
struct ex_stype { // structure definition by struct data type specification
int i; // component is an integer named i
char s[81]; // component is a string named s
};
void set_ds(int x, char nm[], int i); // function to assign values for the s and i components in structure ds[x]
void show_ds(int x); // function to display the elements of structure ds[x]
struct ex_stype ds[2]; // ds is an array each element of which is an ex_stype structure
int main()
{
set_ds(0,"one",1); // assign values to the components of structures ds[0] and ds[1]
set_ds(1,"two",2);
show_ds(0); // display the components of structures ds[0] and ds[1]
show_ds(1);
}
void set_ds(int x, char nm[], int i)
{
ds[x].i=i; // copy argument i to the i component of structure ds[x]
strcpy (ds[x].s, nm); // use the string library function strcpy to copy argument nm to the s component of structure ds[x]
}
void show_ds(int x)
{
printf("name %d is %s\n",ds[x].i,ds[x].s);
}
The first part of the example is the specification of the struct data type used to define the data structure ds
used in the program. The struct ex_stype
data type specifies a structure with an integer component i
and a string component s
.
A global variable ds
is declared as a 2 element array of data type struct ex_stype
in the same manner that would be used for any other data type.
The two functions, set_ds
and show_ds
, illustrate using the dot operator (.
) to assign values to the components of a structure and to access the components of a structure, respectively. For the declaration of variable sx
struct ex_stype sx;
the components of the structure sx
are sx.i
and sx.s
. Similarly, for the structure in item x of array ds
, the components are ds[x].i
and ds[x].s
, where the index in square brackets has to be added to identify which of the structures in the array is being accessed.
Pointers to struct data types can also be used, just like pointers to any other type. When a pointer is for a struct data type, an "arrow" (->
) notation can be used to access elements of the structure addressed by the pointer; for example,
struct ex_stype *sptr;
sptr = &dx;
sptr->i = 10;
strcpy(sptr->s, "example text");
The dot operator can be equivalently used, but has the clumsier notation (*sptr).i
as opposed to sptr
.->
i
Just as for arrays, only pointers to structures, not the structures themselves, can be passed to or returned from functions.
Complex data structures formed as arrays or as structures may be initialized upon declaration with a sequence of constant values contained within curly braces and separated by commas.
Character arrays may also be initialized as a string from a string of characters enclosed in (double) quote marks.
In initialization of a one dimensional array, the length (inside the square brackets) can be left blank, in which case the allocated length is determined by what is assigned to the array. The declaration for a multi-dimensional array must specify the size of all dimensions after the first dimension. If a length is specified, and initialization data overflows that length, a warning is issued and the excess data is ignored. If the initialization data falls short of the specified size the rest will be indeterminate.
The following example illustrates a variety of different initializations:
/* declare global variables of various types */
int i = 50; // single basic variable
int *ptr = NULL; // single basic pointer variable
int x=1, y, z=3; // multiple basic variables
double farr[3]= {1.2, 3.6, 7.4}; // one dimensional array
int iarr[5]= {1, 3, 7}; // one dimensional array, last 2 indeterminate
int jarr[]= {2, 4, 6, 8}; // one dimensional array, derived length 4
char carr[2][3]={{'a', 'b', 'c'}, {'b', 'd', 'f'}}; // two dimensional array
int xarr[2][5]={{1, 2, 3}, {2, 4, 6}}; // two dimensional array, last 3 rows indeterminate
int yarr[][2]={{1, 2}, {2, 4}, {3,6}}; // two dimensional array, derived size 3x2
char c[]="A B C"; // string, array size 6=5+1
char sarr[5][10]={"a b c","d","e f"}; // array of strings, first 6 characters of row 1 initialized, first 2 of row 2, first 4 of row 3, last 2 rows indeterminate
struct employee {
char name[31];
double weight;
struct employee *nextrec; // pointer (self-referential) to the struct for another employee
}
struct employee a_node = {"John Doe", 165.4, NULL}; // perhaps a linked list node
struct elist[2] =
{{"JFK", 163.1, NULL }, {"LBJ"}}; // not everything has to be initialized
// example function which declares local variables of various types, initialized from global variables
int f_locals()
{
int x = i; // local x, global i (value 50)
int y = yarr[2][1]; // local y, global yarr (value 6)
int *iptr=&i; // local iptr, address of global i
struct employee wx={"Jane Doe", 115.2, &a_node}; // local struct wx, pointed to global a_node
char lc[]=sarr[2]; // local string lc with initial value "e f" copied from length 4 string stored in sarr[2]
. . .
}
C has unary operations (one argument), binary operations (2 arguments), and even a ternary operation (3 arguments) for performing actions upon data used in a program. Expressions are combinations using one or more operations. Statements incorporate expressions, assignments, function calls, and control flow constructions to form blocks of code for a C program and are terminated by a semi-colon (;
).
Each of the data types has a set of operators for operations that may be performed on expressions for that data type (e.g., x + y
is an expression).
The following operators are provided for integer data:
+
, subtraction -
, multiplication *
, division /
. >
, less-than <
, equality ==
, greater-than-equal >=
, less-than-equal <=
, modulus %
. ||
, logical-AND &&
, logical-NOT !
.
&
, bitwise-OR |
, bitwise-exclusive-OR ^
, bitwise-NOT (one's complement) ~
, left shift <<
, right shift >>
. +
, additive inverse -
, increment (prefix or postfix) ++
, decrement (prefix or postfix) --
x && y
, if x is FALSE, the value returned by the operation is FALSE without checking y since logically this is enough to determine the result has to be FALSE. Similarly, for x || y
, if x is TRUE, the value returned by the operation is TRUE without checking y since logically that is sufficient for determining the result is TRUE. In all other cases, the value for both operands is checked. This can be useful if x is being used as a TRUE/FALSE flag where y may initially be invalid when x is FALSE.
Integer promotion is the action of "promoting" an arithmetic operand to a 32 bit integer, similar to type casting or coercion. Unlike its counterpart, unary -
, it is seldom used.
The following subset of integer operators are provided for floating point data:
+
, subtraction -
, multiplication *
, division /
. >
, less-than <
, equality ==
, greater-than-equal >=
, less-than-equal <=
. For the KIPR Link, floating point operations are implemented in software, which means they are significantly more resource intensive than integer operations. For this reason, floating point values should not be substituted for integers in iterative processes (such as a loop counter).
There is a large selection of math functions in the C library for performing calculations that are inherently floating point. For example,
double a, x,y;
x = sqrt(2); // x is assigned the square of 2
x = log(5); // x is assigned the base e logarithm of 5
x = log2(8); // x is assigned the base 2 logarithm of 5
x = exp(3); // x is assigned e cubed
x = exp2(3); // x is assigned 2 cubed
y = pow(x,0.333333); // y is assigned the (approximate) cube root of x
x = sin(3); // x is assigned the sine of 3 (radians)
a = asin(0.123); // a is assigned the primary angle (in radians) having sine 0.123
There are many more functions in each of these categories, plus functions for other kinds of manipulation of floating point data. The section below for KIPR Link Library Functions includes many of these under the "Math" category designation.
For C, a character is an 8-bit unsigned integer whose value is one of the 256 ASCII character codes (ASCII is the acronym for American Standard Code for Information Interchange). Integer operations can be performed on character data since if a character variable is used in an integer operation, it is automatically coerced from an 8-bit unsigned integer into a (positive) 32-bit integer for the computation. ASCII encodes 0 ... 9 in order, then later A ... Z in order, and then later a ... z in order, to enable easy alphabetizing using integer comparison. Adding 32 to an upper case character will convert it to lower case.
When a value is stored into a character variable, it is coerced into an 8-bit character (by truncating the upper bits). Since character data occurs in 8-bit bytes, character string data is stored in consecutive byte locations in memory; i.e., treating memory as character data provides a means to address and step through memory one byte at a time.
The basic assignment operator is =
. The following statement adds 2 to the value of a.
a = a + 2;
The compound assignment operator +=
does exactly the same thing; i.e.,
a += 2;
All of the following binary operators can be used in a compound assignment:
+, -, *, /, %, <<, >>, &, ^, |
The increment operator ++
increments its operand by 1. When used prefix (++x
) it has a different semantic interpretation then when used postfix (x++
). The simple cases
x++;
++x;
x = x + 1;
x+=1;
are all equivalent.
When used in a more complex expression, the value of++x
and x
change before the result is used in the expression. For x++
, the value of x++
and x
change after the result is used in the expression. For example, for the following constructions using ++x
and x++
x = 3; printf("%d %d\n", x, ++x);
x = 3; printf("%d %d\n", ++x, x);
x = 3; printf("%d %d\n", x, x++);
x = 3; printf("%d %d\n", x++, x);
x = 3; printf("%d %d\n", x, x+1);
the displayed text will be
4 4
4 4
4 3
3 3
3 4
Confusing? To understand this you also have to know the arguments of
The difficulty for explaining what appears at first glance to be straight forward code illustrates why the use of these operators in normally limited to the simpler cases.++x
argument is cleared before values are passed to printf
and by incrementing beforehand both argument values are set to 4.++x
argument is cleared before values are passed to printf
.
x++
argument is rightmost and gets the current value 3 of x
because the increment occurs afterwards. The new value of x
(4) then becomes the value used for the x
argument.x
argument is rightmost and has value 3. The x++
argument sets the value 3 before it increments. The value of x subsequent to the printf
is 4.++
, but notice it also doesn't increment x.
The interpretation for a combination such as x+++y
is dependent on the compiler being used so such variations are avoided, although not prohibited. [So which is it? (x++)+y
or x+(++y)
? The answer could be neither!]
The decrement operator --
decrements its operation by 1. Its semantics mirror those of ++
.
&
) returns the memory address pointing to a variable, array component, structure component, or function regardless of its data type.*
) applied to a pointer accesses the referenced address as an object for the data type of the pointer, either for assigning a value to it, or for utilizing its value.[ ]
) brackets operator accesses the component of the array given by the index enclosed within the brackets..
) dot operator accesses, by name, the specified component.
->
) arrow operator applied to a pointer to the structure accesses the referenced component (by name) of a structure (for structure ds
with component named component i
, ds.i
and (&ds)->i
are equivalent.As per Kernighan and Ritchie's book, the following table summarizes the rules for precedence and associativity for the C operators. Operators listed earlier in the table have higher precedence; operators on the same line of the table have equal precedence and when used without parentheses are cleared left to right.
Operator | Associativity |
() [] -> . |
left to right |
! ~ ++ -- - * & (< data-type>) sizeof |
right to left |
* / % |
left to right |
+ - |
left to right |
<< >> |
left to right |
< <= > >= |
left to right |
== != |
left to right |
& |
left to right |
^ |
left to right |
| |
left to right |
&& |
left to right |
|| |
left to right |
?: |
right to left |
= += -= *= /= %= &= ^= |= <<= >>= |
right to left |
, |
left to right |
+, -,
and *
have higher precedence than their binary forms.
The order in which a computer executes machine-level operations stored in memory is normally sequential, with the exception of those operations which redirect the flow of executrion to continue elsewhere in computer memory. This capability is reflected in C programs by control flow statements which serve to redirect the sequential step by step flow the program would follow otherwise. There are two classes of control flow commands, selection and iteration. Selection commands use a condition test to determine what statement or block of statements to execute next (if-else
, switch, and the ternary operator ?:
). Iteration commands use a condition test to determine how many times to repeat the execution of a statement or block of statements (while, for, do-while
). C also provides means for exiting a control flow program structure independent of the condition test (break) and to skip the rest of a block, continuing to the next iteration (continue).
Each C statement is terminated by a semicolon. A program block is a sequence of statements grouped together inside curly braces. Variables may be defined inside a block and are local to the block (their scope does not extend outside of the block). The object for a control flow statement may be a single statement or a block of statements, except for ?:
, which as an operator applies to expressions rather than statements.
The if statement is a selection statement for making yes/no and either/or decisions, For an either/or decision, the if statement is paired with an else statement (which can only be used in a pairing with if).
The syntax for if is
if (<expression>)
<statement or block>
When paired with else the syntax is
if (<expression>)
<statement or block>
else
<statement or block>
When the <
expression>
evaluates as TRUE (i.e., is not equal to zero), then the <
statement or block>
that follows if is executed.
When the <
expression>
evaluates as FALSE (i.e., is equal to 0), then the <
statement or block>
that follows if is not executed and if there is an else paired with if, then the <
statement or block>
; that follows else is executed.
In effect, these constructions use syntax that corresponds to how similar logic is expressed in English, such as
?:
The ternary operator ?:
provides a compact means for expressing if-else logic where assignment statements are employed as the if-else actions. For example, the following if-else
construction assigns the larger of variables x
and y
to variable z
:
if (x > y)
z = x;
else
z = y;
This same construction can be implemented by using the ternary operator ?:
z = (x > y) ? x : y;
The ternary expression is formed from a condition test (x < y
) followed by a question mark (?
), then the expression (x
) to assign to z
if the condition evaluates as TRUE, separated by a colon (:
) and then the expression (y
) to assign to z
if the condition evaluates as FALSE. Strictly speaking, given the low precedence level of the operator, the parentheses around the condition could be omitted, but are advisable to facilitate identifying the operator's three operands.
ANSI C requires that only the expression whose value is to be assigned to z be evaluated, just as in the if-else
equivalent. The ternary operator is frequently used for obtaining absolute value; e.g.,
absx = (x < 0) ? -x : x;
The while statement in C is the iteration statement most commonly used for managing program loops. A loop is a sequence of program logic that is repeated multiple times (0 or more); for example,, a loop might be used to print out the elapsed time second by second while waiting for a button to be pressed.
The syntax for a while statement loop is
while (<expression>)
<statement or block>
When the <
expression>
evaluates as TRUE (i.e., is not equal to zero), then the <
statement or block>
that follows while is executed, after which the process repeats, starting with evaluation of the <
expression>
again.
When the <
expression>
evaluates as FALSE (i.e., is equal to 0), then the <
statement or block>
that follows while is not executed and the program moves on to the statement following the <
statement or block>
for the while.
Note that the loop continues until something happens that causes the <
expression>
to evaluate as FALSE (e.g., a button is pressed or a counting variable has exceeded its limit). It is up to the programmer to ensure that within the body of the loop (or otherwise) the condition will eventually be FALSE (otherwise the loop will continue until the program is halted by external intervention).
A loop that doesn't terminate is termed an infinite loop. An infinite loop is one that will continue its iteration until the program is halted. An indefinite loop is one whose iteration will continue until some external action occurs (such as a button press). It is easy to create an infinite loop using while. For example,
while(1);
forms an infinite loop because the <
expression>
for the while is 1 which is always TRUE, and the <
statement or block>
is empty (;
), providing no means other than a forced program halt to end the ongoing loop iteration (which is repeatedly doing nothing).
A common error to avoid when programming a while statement loop is exemplified by the following:
while (i < 10); // misplaced semi-colon
x[i++] = i;
In this case, the misplaced semi-colon for the while has made its <
statement or block>
component empty. Assuming i
is less than 10 when the loop starts, this semantic error makes the loop infinite and the program will "hang" at this point, requiring a forced halt to end it. For ideas on how to locate this kind of semantic error for a program, see the section below on program debugging.
The break statement in C provides a means for a program to break out of executing statements within a loop structure or a multi-way selection to proceed with execution of the program statements that come next. A condition test for break is sometimes used within what would otherwise be an infinite loop to break out of it. The break statement applies to while, for, do-while
, and switch.
For example,
int i; // counter will need some initial value while (i < 100) { // has the counter reached its limit? printf("cnt = %d\n", i++); // show the counter and increment it if (side_button() != 0) break; // exit if the side_button is pressed msleep(1000); // pause for one second }
uses a break to exit the while loop.
The continue statement in C provides a means for a program in a while, for, or do-while
loop to skip the balance of the current iteration, continuing to the next iteration if the loop condition test doesn't end the loop.
For example,
int i; // counter will need some initial value while (i < 100) { // has the counter reached its limit? if (i <= 0) { // don't start cnt until i reaches 1 i+=1; continue; } printf("cnt = %d\n", i++); // show the counter and increment it if (side_button() != 0) break; // exit if the side_button is pressed msleep(1000); // pause for one second }
The return statement in C both ends a function and provides a means for the function to return a value to the calling function. A function also ends once its terminating brace is reached or if a return statement having no return value is executed. The calling function can ignore a returned value in any case.
The for statement in C is normally used for managing loops that employ an iteration counter. A while statement loop could also be used for this purpose, but in many cases the program logic is clearer if a for statement is employed to control the loop.
The syntax for a for statement loop is
for (<expr-1>;<expr-2>;<expr-3>)
<statement or block>
The behavior of the for statement loop is equivalent to that of the following while statement loop:
<expr-1>; // initialize the counter (prime the pump)
while (<expr-2>) { // has the counter reached its limit? (have we pumped enough in?)
<statement or block>
<expr-3>; // increment the counter (pump some more in)
}
For example, the following code counts assigns values from 0 to 99 to an integer array
int i, arr[100];
for (i = 0; i < 100; i++)
arr[i] = i;
The switch selection statement for C is used to select one of a series of case targets. The case targets for a switch statement are grouped together inside curly braces as a program block. Each case target is followed by a series of C statements which represent that case. The switch selection in effect skips past cases until it reaches the one selected, and program execution continues from there. If a break statement is in the statements following a case target, then the program exits the switch statement's block at that point, in effect skipping any remaining cases.
if else selection is used to select one of two cases. switch selection is used to select 0 or more cases.
The syntax for a switch statement is as follows:
switch (<expression>) { // switch to the case target whose <constant> matches the value of the <expression>
case <constant>:
<0 or more C statements>
break; // optional - otherwise, program execution continues with the next case
<additional case targets>
default: // optional target used when selection didn't match any case target
<0 or more C statements>
break; // optional - otherwise, program execution continues with the next case
}
The first case target whose <
constant>
matches the value of the expression for the switch statement is the one selected. Program execution continues from the selected case target until either a break statement is encountered, or the end of the switch program block is reached. If there is no case match, then the default target is selected. Absent a default target and a case match, the switch program block is skipped. Putting in a default case with a break statement is considered to be good form, and recognizes the likelihood of more cases being added under program maintenance.
Normally, each case is ended with a break statement since the primary purpose of the switch statement is to make the choice of one of several possible cases to execute; for example,
char answer[81];
switch(answer[0])
{
case 'y':
printf("answer was yes\n");
break;
case 'n':
printf("answer was no\n");
break;
default:
printf("answer was not understood\n");
}
If user input "yes sir" is captured in the character string answer
, then the text "answer was yes" will be printed to the screen. If if was "no sir", then the "answer was no" would have been printed, and if it was "No sir", the text "answer was not understood" would have been printed (since C is case sensitive, 'n' ≠ 'N').
The do-while statement in C is less commonly used for controlling loop iteration than the while and for statements.
The syntax of a do-while statement is as follows:
do
<statement or block>
while (<expression>);
In contrast to the while and for statements, the body of the loop for a do-while statement will be executed at least once since the <
expression>
controlling loop iteration is evaluated at the end of the loop rather than at its beginning. Sometimes program logic for a loop is best expressed by having the condition test occur at the bottom of the loop. For example,
do
printf(".");
while (seconds() < 5);
The logic insures that "." will be printed at least once (so something will always be printed), in contrast to
while (seconds() < 5)
printf(".");
for which nothing will be printed if the value returned by the function seconds
already exceeds 5.
Many authors have expressed opinions on programming style, which for C generally consists of how key elements of a program are exhibited, how comments are handled, and how white space is employed, most notably in how block structures are offset by placement and indentation. The manner in which a program is written affects its readability, ease of understanding, and effort for debugging, enhancement, and maintenance.
The most commonly used style, K&R Style, reflects how programs are presented in Kernighan and Ritchie's book on C (The C Programming Language (2nd Edition)). This style, or a variant, is advocated by most C programming experts and is the style observed in this manual.
As noted earlier, the term "white space" references combinations of characters that when printed produce a blank area on the display. For C, white space is formed by using characters such as spaces, new lines, and tabs. The white space characters in C are
Character | Print Action |
' ' |
space |
'\n' |
new line |
'\t' |
horizontal tab |
'\v' |
vertical tab |
'\r' |
carriage return |
'\f' |
formfeed |
'\n'
has the same outcome for almost every device, '\r'
may behave the same as '\n'
for some devices but on others simply reposition printing at the start of the current line).
The C compiler either ignores white space that is not imbedded inside double quote marks, or collapses it to a single generic white space character for compilation purposes. This means that other than for character strings, white space can be used for things like indentation that improve program readability. White space is required where it is needed to separate adjacent objects, but if another separator such as a parenthesis, comma, curly brace, square bracket, semi-colon, etc is present, white space is not required.
Good use of indentation offsets program blocks, not only making a program more easily understood, but making it easier to determine if the curly braces are matched and block structures will produce the desired semantic results. From the C compiler's point of view,
/* Simple example: C Programmer's Manual */
int main()
{
printf("Hello, world!\n"); /* simple example */
}
and
/* Simple example: C Programmer's Manual */int main(){printf("Hello, world!\n");} /* simple example */
are equivalent, but it is pretty clear the use of white space to set off the elements of the program in the first version makes it more understandable than for the second. This distinction becomes even more important when nested selection and iteration statements are being employed. For an admittedly extreme example, the following construction as presented will compile, but from a human readability point of view is almost incomprehensible:
switch(i){case 0:while(j++<100){k++;if(k>10)break;}break;case 1:if(j>50)k--;else while(j-->=0)k++;break;}
In addition to better presenting overall program logic, use of a consistent programming style facilitates debugging programs. Just as writers develop styles intended to make the text they produce more easily read and understood, programmers develop styles for making their programs easier to read and understand, but with the added objective of making them easier to debug. The KISS IDE program editor provides facilities for automatic indentation that reflects common practice for C programmers.
Comments are the means for programmers to integrate documentation into a program. The lead comment typically specifies the purpose of the program. For large projects, it is a common practice to also include a (bracketed) comment on program history, in particular reflecting the changes that have been made to the program, including by whom, when, and where in the program.
Multi-line comments are typically used to offset description and purpose of major program components. Single-line comments are useful for provide an explanation of logic or reasoning that might be subsequently useful if that part of the program needs to be revisited for debugging or other purposes.
Debugging is the process of correcting syntactic and semantic errors found in a program.
The C compiler cannot compile a program that has syntax errors such as a missing semi-colon or other program construction error. Syntax errors are the ones most easily corrected since the compiler can identify both where they occur and what the problem is. The KISS IDE lists any syntax errors found during program compilation in an error panel below the program code. The interface provides the line number and position within the line identifying the location of the error, with a description of the cause of the error. Since the mistake causing the first error listed will typically cause additional syntax errors within the program, it is often the case that correcting the syntax causing the first error will fix the rest.
Just because a program compiles does not mean it will do what is intended, which is termed program semantics. A semantic error manifests itself during program execution, either because the program crashes or it produces erroneous results. Semantic errors are caused by errors in program logic or programmer oversights (such as failure to initialize a variable). They can be difficult to track down because it usually is not clear where in program execution the error occurred.
For a program which relies on data inputs, determining the presence of semantic errors requires testing the program for a representative set of possible data inputs, including so-called boundary conditions. For example, if a data input has a range from 0 to 1000, then 0 and 1000 are the boundaries for the data, and one of these, 0, will cause the program to crash if used in the denominator of a division operation. That means that once the part of the program where the error occurs has been located, program logic has to be adjusted to check the data input to see if it is 0 before using it in the division. For complex systems, program testing is an ongoing part of the software development cycle.
Semantic errors that are rare events are difficult to correct, since being able to repeat what causes the error is key to being able to resolve it. Once a semantic error can be reliably repeated, various techniques are employed to determine the cause and to locate where in the program it occurred. Only the basic techniques will be covered here, but operating systems provide debugging tools designed to determine the cause of an error and to facilitate locating where in the program code it occurred.
For the smaller C programs constructed for environments like the KIPR Link, most semantic errors that occur can be corrected quickly using one or both of the following two approaches.
printf
statements at various points in the program code to display the value of selected variables to see if their intermediate values are as expected. For example, in an expression containing the sub-expression (x * y-z)
, displaying the values of x, y
and z
might help discover the sub-expression should have been (x * (y-z))
.For larger programs, a system debugging tool may be called for. Unix systems include the debugging tool sdb
, which has its own command structure that has to be learned to be used effectively.
The C function printf
is used for printing to the KIPR Link display screen, where print output is wrapped to subsequent lines if too long for the display, and is scrolled upward as the capacity of the screen is exceeded. For controlled printing to the display, the KIPR Link Library additionally includes the display_printf
version of printf
, which prints starting at a specified (column, row
) position on the KIPR Link display and which doesn't wrap text which exceeds screen width. The number of rows available for controlled printing is different for the 3 button (A,B,C) version of the KIPR Link program console screen than for the 6 button (X,Y,Z,A,B,C) version.
The syntax of printf
is the following:
printf(<
format-string>, ... );
The <
format-string>
is a character string which includes 0 or more "%" codes. For each % code, a corresponding argument is supplied to printf
after the <
format-string>
to provide the value to be formatted and inserted into the print output in place of its % code.
It is important to note that in resolving % codes printf
ignores the data type of the corresponding argument. In other words, since printf
does not require a data type match between an argument and its corresponding % code, the argument will not be automatically coerced as it would be for an assignment statement. This is particularly important to keep in mind when both integer and floating point values are being used.
The use of % code formatting is best illustrated by some examples.
A printf
statement employing no % codes simply prints out the <
format-string>
as a message; for example,
printf("Hello, world!\n");
The character \n
at the end of the string signifies an advance to the next (or new) line for any further printing by the program. When the bottom of the display is reached, the display is scrolled (up) for each line subsequently printed.
In most environments, integers in C are stored in memory as 32-bit 2's complement integers. The % code used to format the corresponding argument as a ± decimal integer is "%d" (or %i"). For the following example, the value of the variable x
is displayed as a decimal integer, with a leading minus sign if the integer is negative:
printf("Value is %d\n", x);
The code "%d" specifies that the first argument after the initial character string in the argument list for printf
(the variable x
) is to be formatted as a ± decimal integer and inserted in place of "%d" in the printed output. The length of the formatted output will vary depending on the number.
As already seen, the "%d" code is used to format an argument in ± decimal integer form. The "%x" and "%X" codes are used to format an argument in (32 bit) 2's complement form using hexadecimal (hex) digits 0 ..9,A,B,C,D,E,F (each of which represents 4 bits in binary, 0000, 0001, 0010, ..., 1111). Negative 2's complement numbers have a leading 1, so negative 32-bit integers written in hex require 8 hex digits beginning with one of the hex digits 8-F; e.g., -28 when formatted using "%X" is FFFFFFE4. For positive numbers, the space required will vary unless a % code modifier for length is employed; e.g., using the % code "%8X" for the integer 28 will yield "
1C" (8 spaces are consumed).
It needs to be emphasized that a length modifier represents the minimum amount of space that will be used when the formatted result is inserted in the print line. If there isn't enough space, printf
will use more.
If the code "%08X" is used instead of "%8X", leading 0's will replace any leading spaces (so using %08X" for the integer 28 will yield "0000001C" as the formatted result). This applies to "%d" as well; e.g., formatting the numbers -28 and 28 using "%04d" yields as formatted results "-028" and "0028", respectively. A length specifier is employed when numbers are being printed to line up uniformly in columns. If you always want the sign of the number printed, not just when it is negative, the code "%+d" forces the sign to be printed as +
or -
.
For example, the printf
statement
printf("Values are %d, %X, %04x\n", -28, -28, 28);
displays the text string
Values are -28, FFFFFFE4, 001c
The third % code in this printf
("%04x") has a length specifier (plus a leading 0's specifier) and lower case is used for the hex digits. If the value to be formatted requires more space, printf
will override the length specifier. It is up to the programmer when using a length specifier to anticipate number size and make the length specifier sufficiently large.
Examples 2 and 3 are representative of output formats for integers using printf
. There are additional integer output formats (including ones for representing integers using octal digits and for unsigned integers) described in most C references.
The % code used to format the corresponding argument as a floating point number is "%f". In formatting for floating point, printf
rounds the fractional part of the number up according to the number of decimal places used (its precision). The default precision is 6 decimal places. A precision specifier is used to limit this. For example, "%.2f" limits the precision to 2 decimal places. For example,
printf("Values are %f, %.2f\n", 1.266, -1.266);
displays the text string
Values are 1.266000, -1.27
The code "%f" specifies that the first argument after the initial character string in the argument list for printf
(1.266) is to be formatted as a floating point number rounded to 6 decimal places and inserted in place of "%f" in the printed output (since 6 decimal places is sufficient, no rounding occurs). In contrast, the code "%.2f" for the second argument rounds its number (-1.266) to 2 decimal places to yield -1.27.
There are additional floating point % codes described in C references that are used for formatting very large (or very small) floating point numbers in scientific (exponential) notation (±<
mantissa>
E ±<
exponent>
).
The % code "%c" is used to format character data. The % code "%s" is used for character strings, since they are frequently needed for print display.
char header[] = "Data: ", cs='a';
int x=28;
printf("%s%c = %d\n", header, cs, x);
displays the text string
Data: a = 28
where for the printed output "%s" is replaced by the character string at the memory location corresponding to header
, "%c" is replaced by the character given by cs
, and "%d" is replaced by the ± decimal representation of the data given by x
.
The function
is only valide for the KIPR Link and the Simulator. It is like the standard display_printf
function except its first two arguments specify the column and row (zero indexed) on the display where printing starts. The remaining arguments are the same as for printf
printf
. The following example prints
2. KIPR store
starting at column 5 on the 3rd row of the display. Note that the first row/first column of the display has column index 0 and row index 0.
int i = 2;
char nm[] = "KIPR store";
display_printf(4,2,"%d. %s ",i,nm);
The column range for display_printf
is 0 - 41. The row range for the 3 button (A,B,C) case is 0 - 9 and for the 6 button (X,Y,Z,A,B,C) case is 0 - 7.
display_printf
does not wrap, truncating strings that go beyond the end of a row on the display.
The special character '\n
' should be avoided when using display_printf
since it will have unpredictable effects on what is displayed.
When repeatedly printing variable data to the same place on the display, care must be taken to add enough spaces to what is printed to clear artifacts from the previous print; e.g., if one of the following was used,
display_printf(4,2,"%d. %s",i,nm); // NO
display_printf(4,2,"%d. %s ",i,nm); // YES
then if we had already printed "2. KIPR store" and changed nm
to "Staffing" then the first of these would print
2. Staffingre
whereas the second would add enough spaces on the end to clear the artifacts, printing
2. Staffing
% Code | Associated Data Type | Format Outcome |
%d or %i |
int | ± decimal integer |
%x or %X |
int | 2's complement hexadecimal representation |
%f |
double | ± number with decimal point |
%c |
char | ASCII character (low byte for int ) |
%s |
char * | ASCII characters until '\0' is reached |
There are additional % codes for printf
and more complex % code modifiers that can be used in printf
statements. For information on these, see a standard C reference.
Before the C compiler receives a file, it is first passed through the C preprocessor to prepare the file for compilation. The preprocessor clears away comments, shrinks white space to single characters, and processes any preprocessor directives present in the program.
Preprocessor directives are identified by the "#" symbol, which must be the first entry on a line for the directive to be recognized by the C preprocessor.
The two primary preprocessor directive are #include for inserting files into the program code and #define for defining macros that are expanded wherever their names appear in program code.
For a macro call to be recognized by the preprocessor, it's definition has to occur at some point earlier in the program. Macro definitions begin with the macro directive #define and are usually grouped together at the beginning of the file in which they appear. The preprocessor will flag a macro definition as being a duplicate if its name is reused for another #define in the same file. If more than one file is employed for a program, a preprocessor directive applies only within the file where it is defined.
Since the programmer may or may not know what #define commands appear in a #include file, commands are provided to check to see if a macro has already been defined (#ifdef ..
#endif and #ifndef ..
#endif). For example,
#ifndef PI
#define PI 3.1416159
#endif
When the preprocessor encounters a valid macro name in the program code, the macro is called and its name "expanded" to be replaced by whatever text the macro generates; e.g., when the macro name PI is encountered by the preprocessor it replaces it with the text 3.1416159 (for the C compiler to subsequently interpret as a floating point constant).
The directive #undef removes a macro name from the list of defined macros (and otherwise does nothing). It is sometimes employed defensively to guard against the possibility of #include inserting a macro name that conflicts with the name of a function the programmer has defined. The convention employed to minimize the likelihood of this occurrence is to use upper case letters for the names of macros and for nothing else.
The sequential bypass strategy employed by the C preprocessor precludes iterative loop directives, but a limited if-else selection is provided. The directive for this purpose, #if, employs a condition test with computation limited to integer constants, character constants, comparisons, arithmetic and logic operators, and macros names (which are expanded before the condition test is calculated). The section of code selected by a #if directive consists of the lines of code that follow it, continuing until one of the directives #else, #endif, or #elif is encountered. The section of code selected by a #if directive is processed if the preprocessor calculates the value of the condition test to be non-zero; otherwise, the preprocessor omits the section from the code sent to the compiler. The term used to describe this procedure is conditional compilation since the condition test determines whether or not a section of code is sent on to the compiler; for example,
#define CFLAG 1
#if CFLAG==1
display_printf(1,3,"On target ");
#else
printf("On target\n");
#endif
selects which form of printf
to use according to how CFLAG is #defined. CFLAG serves as a "configuration variable" to be set according to the ennvironment where the program will be run.
#elif has the same interpretation as "else if" and requires a condition test. The section of code selected by #elif is terminated by any one of #else, #endif, or #elif also.
#else has the same interpretation as "else", with its section of code terminated only by #endif.
The sections of code selected by the two directives #ifdef and #ifndef discussed earlier can be terminated by #else or #elif as well as #endif.
Preprocessor macros can be used to associate a name with a constant that appears in multiple places in code (e.g., PI or LMOTOR), to simplify a C function call, or to provide a debugging capability that can be turned on or off by use of conditional compilation, among many other possibilities.
The preprocessor #include directive is used to insert either a system header file or a user defined header file into program code. System header files typically provide the prototypes for the pre-compiled functions in standard C library. They also provide configuration variables in the form of macros (e.g., NULL). Some programming environments may include the more common system header files automatically, depending on how much control the programmer is expected to exert over the programming environment. System header files incorporate #ifndef statements to avoid introducing duplicated definitions, since a system header file may appear in more than one user header file included by a function.
If the file name for a #include directive is enclosed in "pointy brackets" (< >
) then the preprocessor searches for the file in the system's directory of header files.
If the file name for #include directive is enclosed in (double) quote marks, then the preprocessor searches for the file in the user's file space. Unless the file is located along the user's file path, the path information for locating the file must also be included (e.g., for a USB stick). The contents of the file can be anything, and will be passed through the C preprocessor while being inserted, which will process any preprocessor directives in the file. For user header files, care needs to be taken with any variable declarations incorporated into the file (to avoid duplicates caused by #includes across multiple program files) and in general function definitions should not be imbedded in header files rather than pre-compiled for a user library. To sum up, when #include is used with a carelessly prepared user header file, a compiler error such as a duplicated global variable name may occur, or a preprocessor error such as a duplicated macro name may occur.
The following provides an example of each kind of #include directive:
#include <stdio.h> // insert function prototypes for system I/O functions
#include "mylib.c" // insert my function library
A few of the more commonly used system header files providing definitions for pre-compiled functions in the standard C library follow:
<stdlib.h>
[numeric conversion, memory allocation, functions such as rand
]<stdio.h>
[I/O functions such as printf
]<math.h>
[math functions such as sqrt
]<string.h>
[string functions such as strcpy
]<time.h>
[date and time functions such as time
]<stdarg.h>
[functions with varying number of arguments (i.e., ones using the ...
argument)]
The #define preprocessor directive specifies a macro definition. The macro definition is limited to one line of code (of indefinite length). Once defined the macro will be expanded wherever it occurs in subsequent program code.
Macros are often used to provide replacement text, where the macro provides a more meaningful name; e.g.,
#define RIGHT_MOTOR 0 // equate RIGHT_MOTOR with 0
#define POWER 90 // equate POWER with 90
If the motor command
motor(RIGHT_MOTOR, POWER);
is used in subsequent code, the preprocessor will expand the RIGHT_MOTOR
and POWER
macros, replace them with 0 and 90, respectively, so the code as prepared for compilation becomes
motor(0, 90);
Global variables could also be used to provide meaningful names for quantities, but preprocessor macros produce slightly more efficient code. The primary advantage in either case is that if testing shows that the motor port or power needs to be changed, it only needs to be changed at one place in the program.
The definition of a preprocessor macro can also specify one or more arguments to be used in expanding the macro. For example,
#define GO_RIGHT(power) motor(RIGHT_MOTOR,power)
defines a macro GO_RIGHT
that takes an argument (power
) and uses the macro RIGHT_MOTOR
in its definition.
If GO_RIGHT(85)
appears in program code, it will expand to motor(RIGHT_MOTOR(85)
which will then expand to motor(0,85)
as the code prepared for compilation.
Superficially, the use of a macro that doesn't have arguments looks like a global variable reference. Likewise, the use of a macro with arguments looks like a call to a function. However, macro expansion is simply one-time text replacement which occurs during preprocessing Compilation resolves global references as memory locations subject to dynamic change during program execution. When called, a function evaluates and interprets its arguments dynamically.
Appropriate use of macros can make it easier to follow the C program logic and can be used to facilitate program testing and modification.
The C preprocessor can be used to select code to be compiled based on logical conditions in preparing a program for compilation. This is called conditional compilation. Conditional compilation is used to select the code that is to be incorporated into a program based on a condition test. For example, unless a macro named DEBUG
has been defined (usually empty; i.e., #define DEBUG
), the precompiler can omit code whose only purpose is for debugging. In particular,
#ifdef DEBUG
printf("Going Left\n");
beep();
#endif
generates the debugging printf
and beep
only if DEBUG
has been defined, in which case the message "Going Left" will be printed and KIPR LINK will beep when program execution reaches this part of its code. If DEBUG
is not defined, the preprocessor will leave the code out and the debugging alert will not occur as the program executes.
Macros can also be conditionally defined; for example, the somewhat more sophisticated debugging macro definition
#ifdef DEBUG // if DEBUG is defined, SHOW(printf("%d",i) generates DEBUG code
#define SHOW(x) printf("DEBUG: "); x // if case: SHOW macro is defined to generate code
#else // if DEBUG is not defined, SHOW(printf("%d",i) generates nothing
#define SHOW(x) // else case: SHOW macro is defined to generate nothing
#endif
defines a SHOW
macro in one of two ways, either to produce debugging printf
statements from its argument, or to produce nothing, regardless of argument.
If DEBUG
has been defined, then a printf
debugging statement used as the argument for SHOW
will be incorporated into the program code. Debugging is activated by adding the #define DEBUG directive to the program, and deactivated by commenting it out. In particular, when DEBUG
has been defined the code
SHOW(printf("%d\n",i);)
inserted at an appropriate point in the program will be expanded by the preprocessor to produce the code
printf("DEBUG: "); printf("i=%d\n",i);)
Absent a definition for DEBUG
the macro SHOW
will expand to produce no code at all.
If the value of i
is 8 and DEBUG
has been defined, the expansion of SHOW
illustrated above will generate code which outputs the debugging display "DEBUG: i=8
".
The standard C library has a large number of precompiled math functions. #include <math.h>
provides the function prototypes for the math functions that operate on floating point numbers, which is most of them. #include <stdlib.h> provides the function prototypes for those which operate on integers. Arguments for trigonometric functions use radian measure for angles rather than degrees (1 degree is 2π/360 radians). The following is a representative list for the available math functions. For more information about what math functions are available, consult a C reference.
Before the Linux operating system can access a file system, it has to "mount" the file system. When a USB flash drive is plugged into the KIPR Link, it is automatically mounted. When the USB flash drive is unplugged it is automatically unmounted. The C Library has a number of functions designed to access files located in mounted file systems. The library functions fprintf
and fscanf
respectively provide a straight forward means for writing formatted output to a file on a USB drive plugged into the KIPR Link, and for reading formatted data from a file on the USB drive. There are a number of file processing commands, including ones for accessing files byte by byte. For a full description of the range of functions available consult a standard C reference book.
To access a file, in addition to the file name, the directory "path" leading to the file has to be known. For the KIPR Link, the directory path to a mounted Flash drive in a USB port is
/kovan/media/sda1/
Files are accessed in C via a pointer of type FILE
, which is defined in the system header file <stdio.h>
. The pointer for a file is established when the file is "opened" for access. If the fopen
function returns a NULL
pointer, it indicates that either the file doesn't exist for the specified file path, or its file system hasn't been mounted (e.g., the USB drive has not been plugged in). Both cases are illustrated in the following program for a USB drive plugged into a KIPR Link. The example otherwise is a program designed to send data to a file, close the file, then reopen the file and retrieve the data to verify a successful write operation. If the file doesn't exist it is created. If it does exist, it is appended to. A user defined preprocessor macro (USB) is constructed to set the file path for the USB drive, illustrating how the preprocessor can be used to potentially simplify program code.
#include <stdio.h> // make sure file I/O is defined
// USBFILE is a Macro defined to preface a file name with the directory path for a mounted USB drive
#ifndef USBFILE
// _STRINGIFY_ is an auxiliary macro which converts the argument into a string (by surrounding it with double quote marks)
#define _STRINGIFY_(x) #x
// the USBFILE macro appends x to the path for the USB drive, then uses _STRINGIFY_ to make the text a string
#define USBFILE(x) _STRINGIFY_(/kovan/media/sda1/x)
#endif
int main()
{
FILE *f; // file pointer f (the macro defining the FILE data type is in <stdio.h>)
// A file for the USB drive named "myfile" is set up using macro USBFILE
char s[81], chkf[] = USBFILE(myfile); // set up the file path for myfile in string variable chkf
int x, data = 2;
// try opening for read ("r") to see if the file exists
if ((f = fopen(chkf,"r")) != NULL) {
fclose(f); // file chkf already exists
printf("Will be appending to USB %s\n", chkf);
}
// (chkf is not open at this point)
// open to append ("a"), which also tests if the USB stick is plugged in
if ((f = fopen(chkf,"a")) == NULL) {
printf("No USB stick detected\n");
return -1; // exit the program
}
// file is now open for append; if it didn't exist it has been created
printf("Sending %s, %d\n", "Field ", data);
fprintf(f,"Field "); // use fprintf to send a text string to chkf
fprintf(f,"%d",data); // now send formatted numeric data using fprintf
fclose(f); // close the file to make sure the output is sent
// now read it back
f = fopen(chkf,"r"); // it exists since we just created it
fscanf(f,"%s %d",s, &x); // read the two data items from the file
fclose(f); // done with file, so close it
printf("Data read is %s: %d\n", s, x);
}
The USB drive can now be removed from the KIPR Link. If not already present, there will now be a file named myfile
on the USB drive, which can be read using a text processor to confirm the write operation was successful.
The KIPR Link Library provides pre-compiled functions for employing the features of the KIPR Link or the KIPR Link simulator. The prototype declarations for these functions are automatically included by the C preprocessor if the KISS IDE target selection for a program is the KIPR Link or the simulator.
The most commonly used KIPR Link Library functions for robot control are ones for accessing sensor ports, operating DC motors, and suspending a program while a motor action is in progress. A quick refresher for the ones typically used follows:
The following is a comprehensive list of the pre-compiled functions provided by the KIPR Link Library. The library functions for using a USB camera, a graphics window, or a USB depth sensor, and those for controlling a Create module, are presented separately.
a_button [Category: Sensors]
|
|
a_button_clicked [Category: Sensors]
|
|
accel_x [Category: Sensors]
|
|
accel_y [Category: Sensors]
|
|
accel_z [Category: Sensors]
|
|
alloff [Category: Motors]
|
|
analog [Category: Sensors]
|
|
analog_et [Category: Sensors]
|
|
analog8 [Category: Sensors]
|
|
analog10 [Category: Sensors]
|
|
any_button [Category: Sensors]
|
|
ao [Category: Motors]
|
|
b_button [Category: Sensors]
|
|
b_button_clicked [Category: Sensors]
|
|
beep [Category: Output]
|
|
bk [Category: Motors]
|
|
block_motor_done [Category: Motors]
|
|
bmd [Category: Motors]
|
|
c_button [Category: Sensors]
|
|
c_button_clicked [Category: Sensors]
|
|
clear_motor_position_counter [Category: Motors]
|
|
console_clear [Category: Output]
|
|
digital [Category: Sensors]
|
|
disable_servo [Category: Servos]
|
|
disable_servos [Category: Servos]
|
|
display_clear [Category: Output]
|
|
display_printf [Category: Output]
|
|
enable_servo [Category: Servos]
|
|
enable_servos [Category: Servos]
|
|
extra_buttons_show [Category: Output]
|
|
extra_buttons_hide [Category: Output]
|
|
fd [Category: Motors]
|
|
freeze [Category: Motors]
|
|
get_extra_buttons_visible [Category: Sensors]
|
|
get_motor_done [Category: Motors]
|
|
get_motor_position_counter [Category: Motors]
|
|
get_pid_gains [Category: Motors]
|
|
get_servo_enabled [Category: Servos]
|
|
get_servo_position [Category: Servos]
|
|
mav [Category: Motors]
|
|
motor [Category: Motors]
|
|
move_at_velocity [Category: Motors]
|
|
move_relative_position [Category: Motors]
|
|
move_to_position [Category: Motors]
|
|
mrp [Category: Motors]
|
|
mtp [Category: Motors]
|
|
msleep [Category: Time]
|
|
off [Category: Motors]
|
|
power_level [Category: Sensor]
|
|
run_for [Category: Threads]
|
|
seconds [Category: Time]
|
|
set_a_button_text [Category: Sensors]
|
|
set_b_button_text [Category: Sensors]
|
|
set_c_button_text [Category: Sensors]
|
|
set_digital_output [Category: Output]
|
|
set_digital_value [Category: Output]
|
|
set_pid_gains [Category: Motors]
|
|
set_servo_position [Category: Servos]
|
|
set_x_button_text [Category: Sensors]
|
|
set_y_button_text [Category: Sensors]
|
|
set_z_button_text [Category: Sensors]
|
|
side_button (or black_button) [Category: Sensors]
|
|
side_button_clicked [Category: Sensors]
|
|
thread_create [Category: Threads]
|
|
thread_destroy [Category: Threads]
|
|
thread_start [Category: Threads]
|
|
thread_wait [Category: Threads]
|
|
x_button [Category: Sensors]
|
|
x_button_clicked [Category: Sensors]
|
|
y_button [Category: Sensors]
|
|
y_button_clicked [Category: Sensors]
|
|
z_button [Category: Sensors]
|
|
z_button_clicked [Category: Sensors]
|
By default the digital ports for the KIPR Link are configured for input. The KIPR Link Library function set_digital_output
is used to configure digital port direction; for example,
set_digital_output(9, 1);
configures port 9 for output and
set_digital_output(9, 0);
configures it for input.
For a digital port configured for output, the KIPR Link Library function set_digital_value
is used to set the port's output value to either 0 (low) or 1 (high); for example,
set_digital_value(9, 1);
sets the output value for port 9 high.
If you have a typical 5mm LED on hand, you can use the following program to operate it. An LED will "turn on" when voltage applied to its anode lead rises above a prescribed level (typically between 1.9 and 3.2V, depending on color). If too much current is passed through the LED, it will burn out (the typical spec is 20-30mA). For the KIPR Link, digital outputs on the SEN rail are sufficiently current limited to operate an LED without burning it out.
The LED's anode is normally identified by having the longer of the two leads. Additionally, the flange at the base of the LED is normally flattened on the cathode side. | ![]() |
/* This is a program to blink an LED plugged into digital port 9 */
int main()
{
printf("LED in port 9\n");
printf("Press side button to quit\n");
set_digital_output(9, 1); // set digital port direction to digital output
while (side_button() == 0) {
set_digital_value(9, 1); // set digital output to 1 (high)
msleep(500);
set_digital_value(9, 0); // set digital output to 0 (low)
msleep(500);
}
set_digital_output(9, 0); // set digital port direction back to digital input
printf("\ndone\n");
}
The KIPR Link Vision System incorporates color vision tracking and QR code identification. A USB web camera is used to provide images to the KIPR Link at a rate dependent on lighting conditions but exceeding 6 frames per second. Using the KIPR Link interface, an arbitrary number of camera configurations containing channels for color vision tracking and/or QR code identification can be defined.
For color vision tracking, images are processed by the KIPR Link to identify "blobs" matching the color specification for each color channel in a camera configuration. A blob is a set of contiguous pixels in the image matching the color specification for the channel.
For each color channel in a selected configuration, the values to be used to identify which pixels in an image match the desired color for the channel are interactively selected from a color spectrum chart to provide a color specification for the channel. Live feed from the camera simplifies the process of determining how much of the spectrum is needed to produce blobs matching the color (e.g., a particular part of the spectrum might include all pixels that are "reddish" in color for a channel to be used for identifying red objects). The spectrum values for the channel are retained with the configuration until the configuration is deleted. See the KIPR Link Manual available from KIPR for information on setting up camera configurations on a KIPR Link controller.
There are three camera image resolutions supported on the KIPR Link, 160×120 (LOW-RES
), 320×240 (MED-RES
), and 640×480 (HIGH-RES
). LOW-RES
is sufficient for most purposes and requires far less processing overhead. For LOW-RES
the upper left corner for the image has coordinates (0,0) and the lower right has coordinates (159,119). The camera image displayed on the KIPR Link is slightly smaller than the actual image size.
KIPR Link Vision Library functions are used to select a configuration and obtain information about the color blobs being identified by its channels, such as bounding box coordinates and pixel density.
In addition to channels for color tracking, a configuration can have channels for identifying QR (Quick Response) codes. A QR code is essentially a 2-dimensional bar code for compactly representing text data. KIPR Link Vision Library functions are provided for decoding any QR code in the image. See the QR code vision functions get_object_data, get_object_data_length
below.
camera_close [Category: Vision]
|
|
camera_load_config [Category: Vision]
|
|
camera_open [Category: Vision]
|
|
camera_open_at_res [Category: Vision]
|
|
camera_open_device [Category: Vision]
|
|
camera_update [Category: Vision]
|
|
get_channel_count [Category: Vision]
|
|
get_camera_frame [Category: Vision]
|
|
get_object_area [Category: Vision]
|
|
get_object_bbox [Category: Vision]
|
|
get_object_center [Category: Vision]
|
|
get_object_centroid [Category: Vision]
|
|
get_code_num [Category: Vision]
|
|
get_object_confidence [Category: Vision]
|
|
get_object_count [Category: Vision]
|
|
get_object_data [Category: Vision]
|
|
get_object_data_length [Category: Vision]
|
Camera functions will not provide meaningful data until the camera has been activated using camera_open
or camera_open_at_res
. For most purposes, the LOW_RES
setting used by camera_open
is sufficient and it serves to reduce frame processing overhead. Once the camera has been activated, the camera function camera_update
is used to obtain the current frame in the camera's field of view. Note that camera_update
is called every time through a camera processing loop to provide a current camera frame for processing.
/* This program points a servo (that is plugged into port 0 and centered on the camera's field of vision) towards an object that fits into the color model defined for channel 0 */
int main()
{
int offset, x, y;
enable_servo(0); // enable servo
camera_open(); // activate camera
camera_update(); // get most recent camera image and process it
while (side_button() == 0) {
x = get_object_center(0,0).x; // get image center x data
y = get_object_center(0,0).y; // and y data
if (get_object_count(0) > 0) { // there is a blob
display_printf(0,1,"Center of largest blob: (%d,%d) ",x,y);
offset=5*(x-80); // amount to deviate servo from center
set_servo_position(0,1024+offset);
}
else {
display_printf(0,1,"No object in sight ");
]
msleep(200); // don't rush print statement update
camera_update(); // get new image data before repeating
}
disable_servos();
camera_close();
printf("All done\n");
}
This program displays the camera image in a 160×120 graphics window centered on the 320×240 KIPR Link display, decoding any QR code found and displaying the result in the space above the graphics window.
// Assume channel 1 is configured for identifying QR codes
// If a QR code is found, it is translated
int main()
{
int r, c, ix, i, lngth;
const unsigned char *img; // variable to hold camera image
camera_open(); graphics_open(160,120); // activate camera and open a graphics window
camera_update(); // get most recent camera image and process it
while(side_button()==0) {
img=get_camera_frame(); // get a camera frame and display it in graphics window
for(r=0; r<120; r++) {
for(c=0; c<160; c++) {
ix=3*(160*r + c); // index of pixel to paint into row r, column c
graphics_pixel(c,r,img[ix+2],img[ix+1],img[ix]); // RGB order by reversing GBR
}
}
graphics_update(); // show the frame
if (get_object_count(1) > 0) { // there is a QR code in view
display_printf(0,0,"QR code: ");
lngth = get_object_data_length(1,0); // decode and display above graphics window
for(i=0; i < lngth; i++) { // print QR code letter by letter until end of data
display_printf(9+i,0,"%c", get_object_data(1,0)[i]);
}
}
else {
display_printf(0,0,"No QR code detected ");
}
camera_update(); // get new image data before repeating
}
camera_close(); graphics_close(); // clean up
}
The KIPR Link (and Simulator) support basic graphical draw operations, which allow a user to create their own user interface if they wish. Functions are provided to draw and color pixels, lines, circles, triangles, and rectangles (non-rotated). The screen and two dimensional objects can also be filled with a single color. Both mouse clicks and cursor location can be detected (on the KIPR Link, a screen tap corresponds to a left mouse click).
Note that any virtual features of the KIPR Link user interface (such as the A,B,C buttons) will be unavailable if obscured by the graphics window (it is advisable to use the side button when doing graphical applications for this reason).
The KIPR Link has functions supporting the use of an ASUS Xtion as a USB depth sensor. | ![]() |
The USB depth sensor provides the Link with a 240×320 row,column (2-dimensional) image of the space in front of it (rows measured down from the upper left corner of the image and columns to the right). The depth functions provide information about the three dimensional space in front of the Xtion, where the space is organized in (x,y,z) coordinates with the x-axis directed right, the y-axis is directed up, and the z-axis directed straight out from the Xtion. Coordinate (0,0,0) is the center of the Xtion face. Coordinate values are given in millimeters (mm). The depth sensor's effective range is from about 1/2 meter to 5 meters.
![]() |
int main()
{
int r, g, b, row, col, val;
depth_open(); // initiate depth sensor
graphics_open(320,240); // open up graphics window (full screen on Link)
while(!get_mouse_left_button()) { // loop until mouse is clicked (screen is tapped)
depth_update(); // get a new depth image
for(row=0; row<240; row++) { // loop through rows
for(col=0; col<320; col++) { // loop through columns in current row
val = get_depth_value(row,col) ; // get distance for pixel in mm
if(val == 0) // if too close or not a valid value, don't color
continue;
else if (val > 1530) { // if more than 1.53m away color in gray scale
val = (5000-val)/20; // range is from 173 on down (towards darker)
graphics_pixel(col,row,val,val,val); // paint using r=g=b (gray scale)
}
else { // and otherwise ...
val=val > 510 ? val-510 : 0; // rerange the distance inward by 510mm
r=val > 510 ? 0 : 255-val/2; // increase red for closer
g=val > 510 ? 255-(val-510)/2 : val/2; // greenish for mid values
b=val > 510 ? val/2-255 : 0; // increase blue for farther
graphics_pixel(col,row, r,g,b); // draw the pixel
}
}
}
graphics_update(); // paint the screen using the new pixel valuess
}
get_mouse_position(&col,&row); // where was screen tapped?
depth_close(); graphics_close(); //close sensor and graphics window
// Display the distance (z coordinate) to the pixels's point in space
printf("Distance to pixel %i,%i is %imm\n\n\n",col,row,get_depth_value(row,col));
}
The functions in the KIPR Link Library for controlling an iRobot Create module provide an interface between a program on the KIPR Link and the Create Open Interface (http://www.irobot.com/filelibrary/pdfs/hrd/create/Create%20Open%20Interface_v2.pdf). Each Create function in the KIPR Link Library is designed to send the Create a series of Open Interface commands that cause the Create to perform some action. A script of Open Interface commands can also be downloaded to the Create to be played later (see the library function create_write_byte
).
The KIPR Link communicates with the iRobot Create via a (TTL) serial connection. Functions included in the KIPR Link Library for the iRobot Create send serial byte code sequences to the Create over the serial connection, making it possible to operate the Create without having to reference the Open Interface guide. These sequences cover the large majority of actions users typically want to have performed by a Create module (e.g., drive forward at a given speed, determine how far the Create has traveled, etc). They also provide a means for the KIPR Link to directly control an iRobot Create.
A Create script is a (limited) sequence of pre-defined iRobot Create byte code commands ordered to perform some set of actions independent of external control. In contrast to other commands, a command to start a script disables serial communications until the script has finished. See the example program below for more information on user defined scripts.
The KIPR Link Library functions for moving the iRobot Create are non-blocking, so when a movement command is sent to the iRobot Create, its trajectory will continue until a different Create movement function is executed on the KIPR Link. In contrast, once a script is started on the Create it will run to completion, during which time the connection to the KIPR Link is ignored. The iRobot Create may also be used to play MIDI music. Up to 16 songs may be downloaded to the iRobot Create from a song array on the KIPR Link. See the Create Open Interface manual for details on note and duration codes.
The library functions for accessing Create sensor data return either the requested data or an error code. A return value greater than 100,000 is used to indicate an error occurred, where the error number is given by 100,000 + <
Create-Serial-Interface-Packet-Number>
. For example, a code of 100,007 indicates an error occurred when requesting bumper or wheel drop sensor status. Sensor packet numbers range from 7 to 42 as described in the Create Open Interface manual.
The Create Open Interface manual has details concerning the physical characteristics of the iRobot Create module and descriptions of how to use the various byte code commands.
get_create_advance_button
[Category: Create Sensor Function]
int get_create_advance_button();
>>
|get_create_bay_AI
[Category: Create Sensor Function]
int get_create_bay_AI();
get_create_bay_DI
[Category: Create Sensor Function]
int get_create_bay_DI();
get_create_cwdrop
[Category: Create Sensor Function]
int get_create_cwdrop();
get_create_infrared
[Category: Create Sensor Function]
int get_create_infrared();
get_create_lbump
[Category: Create Sensor Function]
int get_create_lbump();
get_create_lcliff
[Category: Create Sensor Function]
int get_create_lcliff();
get_create_lcliff_amt
[Category: Create Sensor Function]
int get_create_lcliff_amt();
get_create_lfcliff
[Category: Create Sensor Function]
int get_create_lfcliff();
get_create_lfcliff_amt
[Category: Create Sensor Function]
int get_create_lfcliff_amt();
get_create_lwdrop
[Category: Create Sensor Function]
int get_create_lwdrop();
get_create_number_of_stream_packets
[Category: Create Sensor Function]
int get_create_number_of_stream_packets();
get_create_play_button
[Category: Create Sensor Function]
int get_create_play_button();
>
) is being pressed, 0 otherwise.
get_create_rbump
[Category: Create Sensor Function]
int get_create_rbump();
get_create_rcliff
[Category: Create Sensor Function]
int get_create_rcliff();
get_create_rcliff_amt
[Category: Create Sensor Function]
int get_create_rcliff_amt();
get_create_rfcliff
[Category: Create Sensor Function]
int get_create_rfcliff();
get_create_rfcliff_amt
[Category: Create Sensor Function]
int get_create_rfcliff_amt();
get_create_rwdrop
[Category: Create Sensor Function]
int get_create_rlwdrop();
get_create_vwall
[Category: Create Sensor Function]
int get_create_vwall();
get_create_wall
[Category: Create Sensor Function]
int get_create_wall();
get_create_wall_amt
[Category: Create Sensor Function]
int get_create_wall_amt();
create_spot [Category: Create Built In Script]
|
|
create_cover [Category: Create Function]
|
|
create_demo [Category: Create Function]
|
|
create_cover_dock [Category: Create Function]
|
create_advance_led [Category: Create Music/LED Function]
|
|
create_play_led [Category: Create Music/LED Function]
|
|
create_play_song [Category: Create Music/LED Function]
|
|
create_power_led [Category: Create Music/LED Function]
|
|
get_create_song_number [Category: Create Music/LED Function]
|
|
get_create_song_playing [Category: Create Music/LED Function]
|
|
create_load_song [Category: Create Music/LED Function]
|
/* This is a program to make the iRobot Create drive in a circle with a radius of 0.25 meters at a speed of 200 mm/sec for 10 seconds, displaying the distance traveled around the circle and the angle that the turn covered */
int main()
{
printf("connecting to Create\n");
create_connect();
set_create_distance(0); // reset the cumulative distance traveled
set_create_total_angle(0); // reset the cumulative angle turned through
create_drive(200, 250); // start move in an arc
msleep(10000); // sleep for 10 seconds and stop
create_stop();
printf("\nResults:\n");
printf(" distance = %d mm\n", get_create_distance(0.1));
printf(" angle = %d degrees\n", get_create_total_angle(0.1));
printf("\ndisconnecting from Create\n");
create_disconnect();
}
The iRobot Create has several built in scripts, mostly to serve the needs of its cousin, the iRobot Roomba. The Open Interface provides byte code commands for running these. It also has a byte code command for loading a user defined script onto the iRobot Create along with a byte code command to start it running. The user defined script remains available until the iRobot Create is power cycled.
Unlike high level languages, scripts for the iRobot Create have no provision for flow of control commands such as if and while, but can use commands to wait for an elapsed time, or for a specified distance or angle to be reached, or for an event such as a bump (wait commands are not available except within scripts).
Memory for storing a user defined script is limited to 100 bytes.
In this example, a function is used to download a sequence of byte commands to the iRobot Create to load a script. The example script is designed to cause the Create to move 1/2 meter at 500 mm/sec (uninterruptible). The script definition follows the first two bytes sent to the Create (byte command 152, and a byte whose numeric value gives the number of bytes that follow). Note that the number of bytes for the script is just the count of the create_write_byte
function calls used after the first two. See the Create Open Interface manual for information on the byte commands.
#define RUN_SCRIPT create_write_byte(153) // macro to run the currently loaded script
void make_drive_script(int dist, int speed) {
create_write_byte(152); // specifies start of script definition
create_write_byte(13); // specifies number of bytes to follow,(defining the script)
create_write_byte(137); // drive command (speed and turn radius in next 4 bytes)
create_write_byte(speed >> 8); // send speed high byte (bits 8-15 shifted to 0-7)
create_write_byte(speed); // send speed low byte
create_write_byte(128); // send hex 80
create_write_byte(0); // send hex 00 (special case: turn radius hex 8000 or 7FFF is straight)
create_write_byte(156); // wait for distance done (in mm)
create_write_byte(dist >> 8); // send dist high byte
create_write_byte(dist); // send dist low byte
create_write_byte(137); // stop move by changing speed and radius to 0
create_write_byte(0); // send high byte (0 speed)
create_write_byte(0); // send low byte (0 speed)
create_write_byte(0); // null turn radius
create_write_byte(0); // null turn radius
// end of script (13 bytes)
}
int main()
{
// program to load and test the above script
create_connect();
set_create_distance(0);
set_create_total_angle(0);
make_drive_script(500, 500); // script to move 0.5m at 500 mm/sec
msleep(500); // give serial connection some time
RUN_SCRIPT;
msleep(2000); // allow time for the script to finish (+ some extra)
printf(" distance traveled = %d mm\n", get_create_distance());
printf(" angle turned = %d degrees\n", get_create_total_angle());
create_disconnect();
}
|
|
|
|
|
|
|
|
|
Three functions are included in the KIPR Link Library to assist programmers in writing programs that meet the basic requirements for a Botball game:
wait_for_light(<
port_num>)
, a function for ensuring the robot doesn't begin operating until the starting light comes on.shut_down_in(<
time>)
, a function which ensures the robot stops operating within the time limits for a Botball game.run_for(<
time>, <
func_name>)
, which is used to start a function, but not allow it to run beyond a specified time.For the typical Botball program, the first executable statement will be
wait_for_light(<
port_num>);
If there are any other set up routines being used, then usually wait_for_light
will immediately follow them. wait_for_light
takes the robot operator through a process of using the starting light to set calibration values for the IR light sensor plugged into the specified port. The purpose is to set two values, one to check the sensor reading with light off and the other with light on. The difference between the two values has to be large enough for the sensor to determine if the light is off or on. If the sensor is suitably shielded and is positioned so that a successful calibration is achieved, wait_for_light
calls for "hands off" and blocks further execution until the light is turned on. When "light on" is detected by the sensor, the program resumes and runs the robot according to program design. If problems occur in the calibration process, the user is notifited and the process repeats after a brief pause.
wait_for_light
is usually a call to the Botball function
shut_down_in(<
time>);
which will shutdown all motors and the Create module (if being used) once the specified time has expired, then terminate the program.
In contrast to completely killing the program using shut_down_in
, when program design requires performing some activity after the robot has completed its primary task, the function
run_for(<
time>, <
func_name>);
can be used to execute the specified function, halting it once the specified time has elapsed (an action that occurs only if the function is still running). The basic idea is to separate the primary task out of main
and write it as a function so it can be run using run_for
. One simple instance where this is useful is at game end when a servo needs to remain on to maintain its position. The necessary command can be executed after using run_for
to run the primary task. This approach puts the burden back on the programmer to make sure all other servo activity is halted and all drive motors and the Create module are stopped within the game's time limit.
The library functions for Botball are:
int main()
{
double s;
int i;
/* Botball calibration: determine if light sensor can discriminate between light and dark */
wait_for_light(3); // light sensor in analog port 3
/* Botball timing: limit is 120 seconds; e.g., shut_down_in(119.5); */
shut_down_in(10.5); // stop execution once 10.5 seconds have elapsed
/* Botball program logic would be next ... the stuff below is just to keep the program running for awhile */
display_clear(); // clear display for display_printf
s=seconds(); // system on time at start of run
for(i=0; i<15; i++) {
display_printf(1,1,"%d. time elapsed = %.2f ", i, seconds()-s);
msleep(1000); // sleep for a second
}
display_printf(1,3,"done");
}
The term thread is short for the phrase "thread of execution", and represents a sequence of instructions to be managed by the system as it schedules processing time among running processes. On a single processor machine, like the KIPR Link, the instructions running in separate threads appear to be operating in parallel. Each thread, once started, will continue until its process finishes or until it is forcibly terminated by another process using the thread_destroy
function. Each active thread gets a small amount of time in turn until all of its statements have been executed or it is forcibly terminated. If a thread's process cannot complete all of its statements before its turn is over, it is paused temporarily for the next thread to gets its share of time. This continues until all the active threads have gotten their slice of time and then thread processing repeats. The Link's processor is fast enough so that from the user's viewpoint it appears that all of the active processes are running in parallel.
Functions running in threads can communicate with one another by reading and modifying global variables. The global variables can be used as semaphores so that one process can signal another when it is not in a section of code that might cause a conflict. Process IDs may also be stored in global variables of type thread so that one process can destroy another one's thread if that is necessary program logic (think in terms of a process that is in an indefinite loop monitoring sensors, so it will never finish otherwise).
Since the operating system limits how many threads can be created, it is inadvisable to create threads in a loop. Good thread management is to destroy a thread rather than leaving it hanging around once it is no longer being used. Threads not destroyed by program end may still count against the operating system limit when the program is run again (unless of course the system has been rebooted). This may result in a program which for no observable reason ceases to work properly. It is up to the program to keep track of a thread it creates, and there are no means (short of rebooting) to destroy a thread after the fact.
The library functions for controlling threads are:
This example illustrates how to detect a button press even it occurs while the main process is paused.
int flag=0; // global flag to signal when side button pressed
void chksens() {
while (1) {
if (side_button()) flag = 1;
msleep(100); // check side button every 1/10th second
}
}
main() {
int cnt = 0;
thread tid; // thread variable for holding thread id
tid = thread_create(chksens); // create a thread for chksens
thread_start(tid); // start chksens running in its thread
while (flag == 0) { // button press during sleep is still caught
display_printf(1,2,"elapsed time %d ",++cnt);
msleep(1000);
}
thread_destroy(tid); // remove the thread
display_printf(0,4,"done");
}
The image below is the front of the KIPR Link showing where the analog and digital sensor ports are located.
Examples of digital and analog sensors used with the KIPR Link
![]() IR light sensor (analog) |
![]() IR reflectance sensors (analog) |
![]() IR "E.T." Distance Sensor (floating analog) |
![]() Touch Sensors (digital) |
![]() IR "Break Beam" Sensor (digital) |
|
![]() Slider sensor (analog) |
![]() Potentiometer (Pot) Sensor (analog) |
![]() Ultrasonic rangefinder (SONAR) |
analog(<
port#>)
analog(<
port#>);
analog_et(<
port#>)
;digital(<
port#>);
digital(<
port#>)
analog(<
port#>);
analog(<
port#>);
USB sensors plug into one of the two USB2 ports on the back of the KIPR Link
Specialized function libraries are provided for
![]() A web camera used with KIPR Link |
![]() ASUS Xtion used with KIPR Link |
The image below is the front of the KIPR Link showing where the DC motor ports are located.
![]() Example DC motor used with the KIPR Link |
The image below is the front of the KIPR Link showing where the servo ports are located.
Example servo motors used with the KIPR Link
(hobbyist servo - used as packaged by manufacturer)
![]() Servo motors |
![]() Servo motor plug |
Servos are controlled by using the KIPR Link servo functions. |
When the KISS Platform is installed, in addition to the KISS IDE, a graphical simulator is installed which can be selected as a target for a project being developed using the KISS IDE. The simulator provides a simulated robot and a stylized operating environment for the robot, including a simulated (Botball style) starting light. For motors and sensors incorporated into the simulated robot, side panels show motor and sensor response values. A representation of the KIPR Link console screen is also provided for display output. The simulated robot can be arbitrarily positioned in its environment using keyboard entry and mouse functions, including turning the simulated light on/off.
The simulator provides an effective means for trying out functions in the KIPR Link Library and for testing small examples when learning how to use a library function. More fundamentally, it provides a user friendly means for obtaining visual feedback for those who are learning to program in C.
Perhaps most importantly, the simulator provides means for testing KIPR Link program code before downloading a project to a KIPR Link, speeding up the process of obtaining a working program for a KIPR Link controlled robot. No simulator can anticipate every variation a real robot might encounter in performing its mission, but in general, simulation serves to limit the amount of program tweaking required to attain acceptable performance on the actual robot.
If a program's action does not involve the KIPR Link Library functions, its target can be "My Computer". When KIPR Link Library functions are being used, the target needs to be "My Link Simulator" or an attached KIPR Link. If no KIPR Link is attached, the simulator can be used to see what a program does.
![]() For executing a project already loaded in the Simulator |
When the target for the active project is the simulator
![]() |