Programming in C

See also

Below is a quick overview of the important points when programming in C. Portions of the text below are copied from the course manual used in the prior iteration of the course. The last version of which may be viewed here: Embedded Control Lab Manual, v15.3. This manual is not applicable to this version of the course.

A Simple Program

The following program is similar to the first programming example used in most C programming books and illustrates the most basic elements of a C program.

#include "stdio.h"  /* Include file */

int32_t main(void)  /* begin program here */
{                   /* begin program block */
    printf("Hello World\r\n");  /* Send "Hello
                     World" to the terminal */
}                   /* end program block */

The first line instructs the compiler to include the header file, stdio.h for the standard input/output functions. This line indicates that some of the functions used in the file (such as printf) are not defined in the file, but instead are in an external library (stdio.h is a standard library header file). This line also illustrates the use of comments in C; which is text include for descriptions and is not part of the program. Comments begin with the two character sequence /* and end with the sequence */. Everything between is ignored and treated as comments by the compiler. Nested comments are not allowed.

The second line in the program is the start of the “main function”: int main(void). Every C program contains a main() function; which is the function that executes first. The next line is a curly bracket. Paired curly brackets (“braces”), { and }, are used in C to indicate a block of code which belongs to something. In the case above, the block belongs to the int main(void) statement preceding it.

The printf() line is the only statement inside the program. In C, programs are broken up into functions. The printf() function, called from within the main() function, sends text to the terminal. In our case, the MSP432P401R would send this text over the serial port to a “virtual terminal” on a computer, where we can view it. This line also illustrates that a semicolon, ;, terminates every statement in C. The compiler interprets the semicolon as the end of one statement, and then allows a new statement to begin.

The last line is the closing brace that ends the block belonging to the main function.

This code above, while useful in describing a C program, will not work for the MSP432P401R as the microcontroller must be initialized such the needed functionality for any program is activated; for example, without initializing the hardware functionality for the printf(), no information would be sent to the computer. For our purposes, we will always initialize the microcontroller with the SysInit() function, included in the engr2350_msp432.h header file:

#include "engr2350_msp432.h"  /* Include file */

int32_t main(void)  /* begin program here */
{                   /* begin program block */
    SysInit();      /* Initialize the uC */
    printf("Hello World\r\n");  /* Send "Hello
                     World" to the terminal */
}                   /* end program block */

Note that the above sample code does not contain include the stdio.h header file anymore. For simplicity, this file is included within the engr2350_msp432.h header file and therefore does not need to be included again.

Syntax

There are many syntax rules in C which could fill a full course. Instead, a limited set of basics are provided in this course; enough to successfully complete the laboratories. There are many resources, textbooks or online, to learn more about the C language.

Comments

Comments are an important part of any program; not because they contribute to the actual program functionality but instead allow for localized documentation of the code. Comments are extremely useful for when sharing code between team members, providing modification history and guidance, producing automated documentation documents (e.g., Doxygen), etc. There are two types of comments within C: the line comment and the block comment.

  • Line Comment: This type of comment starts with two forward slashes, //, with all text on the same line to the right of the the // being part of the comment. A newline ends this comment type. For example:

    int32_t main(void) // This is a line comment
    {      // Note that this brace is not part of the above comment
    
  • Block Comment: This type of comment starts with /*, with any text following it being part of the comment until the end of the comment, denoted by */. This allows for many lines to be encompassed within a single comment block and is useful in commenting out large chunks of code quickly. For example

    int32_t main(void)
    {   /* This is the start of a
        block comment that can span multiple lines.
        It can also comment out valid code:
        printf("Hello World\r\n");
        ... and is ended by this: */
    }
    

Variable Declarations

In C, variables must have defined sizes so they can be preallocated into memory, as opposed to python where the variable type will change with what is saved into it. To define a C variable, you first specify the type and then the name of the variable. You can optionally give the variable an initial value as well:

uint8_t foo;
int16_t tst = -163;

In the example above, the variable foo is created as type uint8_t. Similarly, the variable tst is created as type int16_t with an initial value of -163. If an initial value is not specified, do not assume an initial value of 0.

Integers

An integer number can be created using the following syntax for the type:

[u]intN_t

Here, the u is optional: adding the u specifies that the variable is unsigned, meaning that it cannot be a negative number. Omitting the u allows the number to be negative. The N must also be replaced with the desired variable size in bits. Available sizes are 8, 16, 32, and 64. Going back to the first example, this implies that foo is an 8-bit unsigned integer and tst is a 16-bit signed integer. Note that the _t simply denotes that this is a variable type.

You may also see other equivalent variable type names, as there are other standard names. A table providing these names and describing each variable type is given below.

Type

Alternative

Size in Bytes

Minimum Value

Maximum Value

uint8_t

unsigned char

1

0

255

int8_t

signed char

1

-128

127

uint16_t

unsigned short

2

0

65535

int16_t

signed short

2

-32768

32767

uint32_t

unsigned int

unsigned long

4

0

4294967295

int32_t

signed int

signed long

4

-2147483648

2147483647

Floating Point

In cases where integer numbers (e.g., 0,1,2) cannot accurately represent a number (e.g., 3.14), the non-integer types float, and double are available. These should be avoided when not explicitly required as they are (1) more computationally expensive and (2) can lead to integer errors. Both can represent negative numbers (there is no “unsigned” version). See the table below for more information:

Property

float

double

Size

4 bytes

8 bytes

Smallest +Value

~1.4 x 10-45

~4.9 x 10-324

Largest +Value

~3.4 x 1038

~1.8 x 10308

Digit Accuracy

6

15

Arrays

It may be necessary to store a list or table of values of the same type that you want to associate in some way. An array can be used to do this. An array is a group of memory locations all of the same name and data type. Each memory location in the array is called an element and is numbered, or indexed, with the first element as number “0”. Note: Be aware, though, that arrays can quickly use up the available variable space (more applicable for smaller microcontrollers). The array is declared with the type of data to be stored in the array as follows:

<type> arrayname[maxsize];

For example, the line

uint16_t values[10];

would declare an array named values that can store up to ten uint16_t type values (values[0] to values[9]). An array can be initialized with values when it is declared, or it can later be filled with data by the program as follows:

int16_t c[5] = {25, 10, 35, 2, 17}; // c[0]..c[4] is filled with listed values
int16_t f[5] = {0};                 // f[0]..f[4] is filled with zeros
f[2] = 16;                          // Fill f[2] with the value 16.
f[3] = c[1];                        // Fill f[3] with the value stored in c[1]

Once the array is initialized, it is only possible to change one element at a time; it is not possible to extract several elements at once. Further, the array size cannot be changed.

Arrays can also have multiple dimensions. A simple example is an array with multiple rows and columns. These arrays can also be initialized and filled as follows. The simplest way to reset an entire array within a program is using for() loops that clear every element individually.

int16_t data[2][10] = {{0},{0}};    /* initialized data to have 2 rows and
                                        10 columns filled with zeros */
data[0][5] = 26;                    // Store 26 in row 0, column 5

Operators

Operators are symbols in C that perform specific operations on input(s). Unsurprisingly, many mathematical operators, such as + and - exist in C; among many others. The lists of operators below are not necessarily complete. Operators can be combined within a single C statement as well to form complex equations, etc., using standard order of operations and parenthesis:

int16_t a,b,c,d;
...
a = b + 5;
c = (20 - a) * b;
d = b / (3.1415 * ( a + b));

Mathematical Operators

The symbols used for many of the C mathematical operators are identical to the symbols for standard mathematical operators, e.g., add “+”, subtract “-”, and divide “/”.

Operator

Description

+

addition

-

subtraction

*

multiplication

/

division

%

modulus

The use of all of the operators above should be obvious except for maybe %. This operator performs a division; however, the output is the remainder of the division, not the quotient (assuming integer math). Further, it should be noted that the common symbol for raising to the power of, ^, is not included here. The operator ^ does exist in C, but it a bitwise XOR (see below).

Relational, Equality, and Logical Operators

Two values may be compared to produce a simple “True” or “False” output. These are commonly used within if statements, while and for loops, etc. Each of the operators listed below have two arguments, one on each side of the operator. The output of these operators is always either 0x01 for “True” or 0x00 for “False”.

Operator

Description

Operator

Description

==

equal to

!=

not equal to

<

less than

>

greater than

<=

less than or equal to

>=

greater than or equal to

||

logical OR

&&

logical AND

Bitwise Operators

These operators perform operations on the individual bits of two values as opposed to comparing the values directly. This means that when operating on two values, bit 0 is compared to bit 0, bit 1 is compared to bit 1, and so on, with the result of each comparison being stored in the output bit 0, bit 1, etc.

Operator

Description

Example

Result

|

bitwise OR

0xCC | 0x0F

0xCF

&

bitwise AND

0x88 & 0x0F

0x08

^

bitwise XOR

0x0F ^ 0xFF

0xF0

<<

left shift

0x01 << 4

0x10

>>

right shift

0x80 >> 6

0x02

Unary Operators

Some operators exist that operate on only one argument. These include those listed in the table below.

Operator

Description

Example

Equivalent

var++

Post-increment

b = a++;

b = a; a = a + 1;

++var

Pre-increment

b = ++a;

a = a + 1; b = a;

var--

Post-decrement

b = a--;

b = a; a = a - 1;

--var

Pre-decrement

b = --a;

a = a - 1; b = a;

+var

unary plus

+a

a (no change)

-var

unary minus

-a

-a (negative of a)

~

bitwise inverse

~0x5F

0xA0 (ones complement)

!

logical inverse

!(True)

(False)

Notes:

  • The increment/decrement operators can be used without an assignment (=) to increment/decrement a single variable quickly:

    int32_t b = 0;
    b++;   // b is 1 after this line.
    
  • Be careful using ~. When performing this action on an integer type smaller than 32 bits, the actual math will be performed using 32 bits. This will cause the more significant bits to have value of 1 and may cause some operations to break. This is a common issue when using this operator in if statements. Below is two examples and possible fixes:

    uint8_t a = 0xAB;   // = (00000000) 10101011
    // Erroneous:
    uint16_t b = ~a;    // =  11111111  01010100
    // Correct:
    uint16_t b = (~a) & 0x00FF; // = 00000000 01010100
    
    // Erroneous:
    if( ~a ) {...} // Tests all 32 bits
    // Correct:
    if ( (~a) & 0xFF ) {...} // Forces bits 32-8 to 0,
                // True/False only affected by bits 7-0
    

Shorthand Operator Notation

Most of the above operators have a special shorthand notation. This is only applicable to the case where the desired statement has the form:

val1 = val1 [] val2; // [] is an operator, e.g., +, -, >>, etc.

where val1 is a variable, val2 is either a variable or a numeric value, and [] is the desired operator. To be clear, this case describes where a variable is modified and saved into the same variable name. The shorthand notation is:

val1 []= val2; // This is equivalent to: val1 = val1 [] val2;

For example:

int16_t a,b,c;
...
a += 1; // Add 1 to a, same as a++ and a = a + 1;
b *= 10; // multiple b by 10. Same as b = b * 10;
c >>= 4; // Right shift c by 4. Same as c = c >> 4;

Loops

A program usually has many sections of code that should be repeated either a specific amount of time, until a condition it met (e.g., a button is pressed), or indefinitely. These repeating chunks can be implemented with either for loops or while loops:

while loops

while ( condition ) {
    ...
}

While loops will continually run the associated code (within the braces) as long as condition is True. The value of condition will be checked before the start of code block for each iteration and will not be checked during execution of the code block. When condition evaluates to false, the program will immediately exit the loop, with program execution continuing with the code after the code block. An example:

uint8_t var = 10;
while(var < 25){
    printf("Value of var: %u\r\n",var);
    var++;
}

This loop will result in the terminal showing the printf statement printing the value of var from 0 to 24 on the terminal. When the value of var is incremented from 24 to 25, the loop sees that the loop condition, var < 25, now evaluates to false and therefore will stop execution of the loop.

The most common format for a while loop is:

while(1){
    ...
}

This is called an “infinite loop”, as the loop condition will always evaluate to true; implying that the code block should repeat indefinitely.

for loops

for ( initialize_statement ; condition; post_statement ) {
    ...
}

For loops are most useful when the repeating code needs to be evaluated a known number of times. A for loop can be rewritten as a while loop as shown below:

initialize_statement;
while( condition ) {
    ...
    post_statement;
}

Considering the while loop format as given above, it should be clear what each portion of a for loop declaration does: The initialize_statement is evaluated once prior to starting the actual loop, the post_statement is evaluated after each iteration of the code block evaluation and the condition is used to end the loop. An example:

uint8_t i;
for( i = 0; i < 10; i++ ){
    ...
}

This loop will run 10 times as controlled through the loop variable i. The value is forced to start at 0 (i = 0), it will increment after each loop (i++) and it will stop when i is equal to 10 (i < 10).

The usefulness in using the for loop as opposed to the while loop structure as shown above is that it is more compact and the behavior of the loop is easier to interpret. For instance, the example while loop given above can be rewritten to:

uint8_t var;
for( var = 10 ; var < 25 ; var++ ){
    printf("Value of var: %u\r\n",var);
}

A special case of a for loop is:

for(;;){
    ...
}

In this case, no initialize_statement or post_statement are given and therefore there is no associated evaulation done, implying that these items are optional. The condition statement is also omitted. In this case, the condition is assumed to be true. Therefore, this syntax results in another version of an infinite loop.

Both while and for loops can be written such that there is no code associated with each loop iteration. This is done by either not adding code within the braces or by terminating the loop statement with a semicolon without the braces:

while(...);

This syntax is useful when the microcontroller is waiting for something to happen; for example, a button to be pressed (continually checking the value of a pin) or for a period of time to elapse (checking a timer’s output).

Functions

C functions calls follow the general format:

output = func_name(input1,input2,...);

A function can have no input argument or many input arguments; however, the number of arguments is always fixed for a given function. That is, if function foo(in1) is defined to have one input argument, in1, it must be called with exactly one input argument; otherwise, the compiler will throw a too many/few arguments error. The input arguments are treated as local variables to the function.

A function can only have up to one output. The value for this output is set using the return statement. For example:

return 10;

This code will set the output of the function to the value 10 and end the function. Any code after a return statement call is ignored. Multiple return statements can exist within a function, commonly nested within if/else statements.

A function also has access to all global variables and these may be used as opposed to explicit input and output types.

There are usually two parts to creating a function: the “prototype” and “declaration”. For our purposes, the prototype for a user created function should be placed near the top of the source file (e.g., after the #include statements) and the declaration placed after the main() function. In more complex projects, you would find prototypes listed in .h files (“header” files) whereas the declarations would be found in other .c files (“source” files).

Function prototype

The prototype for a function is used to inform the compiler that the function exists but only gives information towards how the function is called. A function prototype follows the following format:

<output_type> function_name(<arg_type> input1,<arg_type> input2,...);

Input arguments are listed in a comma separated list. For a function prototype, it is not necessary to give the variables names. <arg_type> can be any variable type available within the program (including structs, arrays, pointer, etc.). An empty list of arguments, function_name(), implies that no arguments are required. This is equivalent to the void keyword, which means “nothing”: function_name(void).

The lone output argument can be any variable type except arrays. If no output is generated from the function, then the output type must be void: void function_name(...);.

Prototype Examples:

void foo();     // Basic function, no input or output (e.g., uses global variables)
void foo(void); // Same as above but explicitly noting no input arguments
uint16_t expl(int8_t var1); // Function with 1 input and uint16_t type output
uint16_t expl(int8_t); // Same as above, no input argument name required for prototype
int32_t lots(int32_t a,float b); // Function with two inputs and int32_t output

Function declaration

A function declaration specifies the code that is associated with the function name. The declaration must not exist within another function declaration.

A declaration starts with essentially a copy of the function prototype with all argument names given. Following this, the function code is provided within a set of braces: { }.

All global variables within the file are accessible within the function in addition to the function arguments, which are effectively local variables within the function. The values of any modified arguments are not after the function completes; that is, the original source for the input argument values does not change.

A functions ends when either the close brace, }, is reached or a return statement is evaluated.

Example

#include "engr2350_msp432.h"
...
int16_t foo(int16_t var1,float ratio);  // Function prototype
...
int main(){             // main function
    ...
    uint16_t val,a;     // local variables in main
    a = 10;
    val = foo(a,0.25);  // Function call. Value of a will not change
    ...
}
...
int16_t foo(int16_t var1,float ratio){  // Function declaration
    var1 = var*ratio+2;    // Do some math
    var1 += rand();        // Add a random number to it
    return tmp_val;        // Return the value
}

Structs

In many situations, it is useful to have several variables grouped together such that the association between each variable is easily identifiable; for example, in storing contact information (name, address, e-mail, etc.). A struct is a special structure that allows for this, where a parent variable, the struct contains a group of sub-variables, known as fields. The struct in this case would be created like any other variable: by using the desired type specifier and providing a name; however, the type specifier is no longer the typical uint8_t or float, etc., but a variable type that defines what fields the struct contains. For example, the below code defines a struct type. custom_struct_t with the fields being explicitly defined as well:

typedef struct _custom_struct_t {
    uint8_t field1;
    int32_t field2;
    double field3;
} custom_struct_t;

This struct type can be used to create a struct variable, for example: mystruct, with the defined architecture:

custom_struct_t mystruct;

“Dot notation” is used to access the fields within a struct variable. The format for this notation follows as <struct>.<field> where <struct> is the parent struct variable name and <field> is the desired field to access (both without the <>). Continuing with the mystruct example, the fields of mystruct may be accessed as shown below.

mystruct.field1 = 100;  // set the value of field1 within mystruct
mystruct.field2 += 10;  // add 10 to the value of field2
mystruct.field3 = mystruct.field1*mystruct.field2/0.75;
                // Do some math using field1 and field2 to get field3.

As shown in this example code, the operations performed using dot notation are exactly the same as those that are done with normal variables. This extends to all of the operators as described above.

It is also possible to have structs with fields that are also struct types. This would lead to nested dot notation, such as <struct>.<substruct>.<subfield>. Further, since structs are a type of variable, it is also possible to produces arrays from structs:

custom_struct_t myarray[10];
...
myarray[3].field1 = 123;    // Set field1 of the 3rd struct array element to 123.

Pointers

Certain aspects of the DriverLib require that a struct be passed to a function as a pointer. This basically means that instead of giving the function the value of the struct, we are instead telling the function where the struct is in memory. This is useful for two reasons:

  1. The program does not need to create a complete local variable duplicate of the passed struct. For very large structs, this will save both time and resources.

  2. The struct may be permanently modified within the function; that is, changes to the struct within the function will persist once the function exits.

For this course, you do not need a full understanding of pointers and how to use them. The only thing that is required to understand is when and how to pass a struct, or other variable type, as a pointer into a function.

It is fairly easy to identify when a pointer is required as an input to a function as opposed to a regular variable. This is indicated by an asterisk, *, within the function definition, between the variable type and name. For example, the two prototypes:

uint8_t func1(uint16_t var1,int32_t var2);
uint8_t func2(uint16_t var1,int32_t *var2);

look very similar except that the second argument for func2 has an * before the variable name var2 whereas func1 does not. This indicates that var2 for func2 should be a pointer to a variable of type int32_t. To be clear, the value for var2 would simply be the memory address for where the int32_t variable is stored.

To pass a regular variable into a function as a pointer instead, the “Address of” operator: &. This operator will return the address of the variable it is operating on, effectively creating the value of a pointer. Following with the example functions above, we can then do the following:

uint16_t a = 10;
int32_t b = 1000;
uint8_t c = func2(a,&b);

In this example, the memory location of b is passed to func2.

Tip

Connection to Arrays: While arrays and pointers are thought of as separate entities, it turns out that a pointer is used to make the array. When an array is created:

int32_t arry[10];

The actual array elements are initialized in memory along with a pointer that stores the address of the 0th element of the array. This pointer value is the actual variable value for arry; therefore, arry is a pointer to the beginning of the array and is not the array itself. When the array is indexed, arry[2], the pointer is used to look up the array element.

This then implies that an array may be passed as the required pointer argument as well, such as:

int32_t arry[10];
uint8_t c = func2(a,arry);

Note that the & operator is not used here as arry is already a pointer value. This then becomes a concern for implementation: is func2 expecting an array or a single element? You’ll have to read the documentation for the function. If func2 is expecting only a single element, then only the first element of the array would be accessed (good). If the array is 10 elements long and the func2 needs an array of 20, undefined behavior will occur (bad); which may include modification of unrelated variables stored in memory after the array.

Whitespace and Indentation

Whitespace (spaces, tabs, and newlines) within a C file does not play a significant role in the code functionality, except for a single space being used to separate words/variables/etc. This is opposed to some languages, such as Python, where spaces are used to denote nested code. Whitespace is generally only useful towards producing code that is human readable and easy to interpret. For example, the segments of code below are exactly equivalent:

uint16_t var1,var2;var1=20;/*set initial value of var1*/if(var1<10){printf("var1 is less than 10");}var2=foo(var1);
uint16_t var1,var2;
var1=20;    /*set initial value of var1*/
if( var1 < 10 )
{
    printf("var1 is less than 10");
}
var2=foo(var1);

A special case is the newline character, which is important in the evaluation of preprocessor directives. Preprocessor directives are any statements that start with a # and which do not require semicolon to complete. This includes the #include and #define directives, among others. Further, a newline is also required to end a line comment (//).

Indentation should be used to show the nesting level of each line of code. The generally entails that each code block denoted by a set of braces, { }, be indented to clearly identify the grouping. For example:

Bad/no Indentation:

int main(){
uint16_t a,b,c;
b=100;
for(a = 0;a < 100;a++){
b--;
if(a == b){
c = b;
while(c){
c--;
}
}
}
}

Better Indentation:

int main(){
    uint16_t a,b,c;
    b=100;
    for(a = 0;a < 100;a++){
        b--;
        if(a == b){
            c = b;
            while(c){
                c--;
            }
        }
    }
}

It is common for complex files to have corrupted indentation due to major intermediate changes. Fortunately, fixing indentation can be done automatically within CCS by selecting the lines of code to automatically indent (CTRL+A to select all) and pressing CTRL+I.

C Practice Tasks

A series of simple problems are provided for practice purposes. These may be found here.

Functions

Important

Additional functions will be added as the course progresses.

__delay_cycles

void __delay_cycles(uint32_t cycles);

This function is specific to the MSP432 and is used to enforce a program delay equal to the number of system clock cycles specified by cycles. For this course, the system clock is specified as 24 MHz.

Example:

__delay_cycles(24); // Insert a 1 us delay for clock of 24 MHz

abs

int32_t abs(int32_t number)

Take the absolute value of input number. This function is only useful for integer values; for floating point values, use fabs().


ceil

#include <math.h>

double ceil(double number)

Also known as “ceiling”: returns the smallest integer greater than or equal to the input floating point number.

Related functions: floor


floor

#include <math.h>

double floor(double number)

Returns the largest integer less than or equal to the input floating point number.

Related functions: ceil


getchar

int32_t getchar(void);

This function waits for a keyboard key to be pressed on the connected terminal and returns its value as an ASCII character.

Example:

uint8_t input = getchar();  // Read a keyboard keyboard key
printf("You pressed key: %c\r\n",input);    // Print the key pressed

putchar

int32_t putchar(int32_t character);

This function will print a single ASCII character, specified by character. It will return the value of the transmitted character upon completion.

Example:

uint8_t input = getchar();  // Read a keyboard keyboard key
putchar(input);             // Echo the pressed key to the terminal

printf

printf("ControlString",var1,var2,var3,...);

This function is used to format and send information to the terminal in realtime during the program executation. Formatting of the information is completely specified by the ControlString argument. This control string contains normal text to be printed as well as control characters to perform specific functions (e.g., move to next line) and variable placeholders (format codes), where program variable values will be inserted.

The ControlString is always (in our case) a string of text beginning and ending with double quotation marks:

printf("This is a basic control string");
>This is a basic control string

Adding control commands to the string is done by simply placing a backslash with the control code:

printf("\nThis is a control string with a new line at the beginning and end.\n");
>
>This is a control string with a new line at the beginning and end
>

Adding in variable substitutions is done by inserting the desired variable format code at the desired location and then providing the variable to be printed, in order, as extra arguments to the function statement:

uint8_t a = 0x0F;
int32_t b = -1;
printf("This prints the value of a: %u, and b: %d.",a,b);
>This prints the value of a: 15, and b: -1.

Control Commands

Below is a reduced list of the available control commands.

Command

Function

\n

Move (down) to the next line.

\r

Move to the beginning of the current line.

\t

Send a “tab” character

\b

Send a backspace.

Variable Format Codes

Variable format codes always start with the % sign, followed by a series of numbers and letters specifying how to print the number. There are many different forms these can take. Below is listed a simplistic set of these codes. Also given below is the applicable variable type that may be used with the format code. It should be noted that the applicable variables are not necessarily the only variables that may be used with the code, but are the most applicable (e.g., doesn’t result in data loss).

Format Code

Purpose

Variable Types

%c

Single ASCII Character

uint8_t

%u

Unsigned Decimal Number

<= uint32_t

%d

Signed Decimal Number

<= int32_t

%lu

Unsigned Decimal Number

int64_t

%ld

Signed Decimal Number

int64_t

%x

Lowercase Hexadecimal Number

Does not print 0x prior to value!

<= uint32_t

<= int32_t

%X

Uppercase Hexadecimal Number

Does not print 0X prior to value!

<= uint32_t

<= int32_t

%f

Floating Point (non-integer) Number

float

double

One advanced format code feature that may be useful in the specification of the precision of a %f. This may be done by modifying the format code to %.nf, where n is the number of digits past the decimal point to print. For example:

float pi = 3.14159;
printf("Pi is approximately %.2f.",pi);
>Pi is approximately 3.14.

rand

#include <stdlib.h>

uint32_t rand()

Generates and returns a pseudo-random integer between 0 and 32767. The embedded code used to generate the random number effectively performs the following:

uint32_t next = 1;
// Below two lines evaluated for call to rand();
next = next * 1103515245 + 12345;
rand_number = ( next >> 16 ) % 32768;

The value for next is always set to 1 at the beginning of the code; therefore, rand() return the same sequence of numbers each time the program is run. The value for next can be changed by using srand().

To generate a smaller range of values, the modulus operator, %, can be used. For example: the operation rand()%5 will return a random value within the range of 0 and 4.

Related functions: srand


srand

#include <stdlib.h>

uint32_t srand(uint32_t seed)

Seeds the pseudo-random number generator with the value of seed. In respect to the code provided in rand, srand(seed) will change the value of next to seed. This will cause successive calls to rand() to generate a different sequence of pseudo-random numbers, dependent on the value of seed.

Related functions: rand