Chapter 5: Introduction to C Programming
Embedded Systems - Shape The World

Jonathan Valvano and Ramesh Yerraballi

 

This chapter covers the C Programming language starting with the structure, constants and variable declarations, the main subroutine, simple input/output, arithmetic expressions, Boolean expressions, the assignment statement, the while loop and lastly simple functions with at most one input and one output.

Learning Objectives:

  • Know the elements of a C program: What goes where, what are the syntax rules
  • Know declarations: simple data types, char, short, long (unsigned and signed)
  • Know the form of the mandatory main subroutine for a program to be executable
  • Know the basic assignment statement: variable = expression;
  • Know how to use printf and scanf for I/O.
  • Know the basic form of the if-statement and the while(1) statement and how they use the conditional Boolean expression.
  • Know the importance of functions and understand the use with inputs and outputs.

                  

 Video 5.0. Introduction to Programming in C

  

5.0 Introduction

This course presents the art and science of designing embedded systems. In this module we will introduce C programming. If you need to write a paper, you decide on a theme, and then begin with an outline. In the same manner, if you design an embedded system, you define its specification (what it does) and begin with an organizational plan. In this chapter, we will present three graphical tools to describe the organization of an embedded system: flowcharts, data flow graphs, and call graphs. You should draw all three for every system you design. In this section, we introduce the flowchart syntax that will be used throughout the class. Programs themselves are written in a linear or one-dimensional fashion. In other words, we type one line of software after another in a sequential fashion. Writing programs this way is a natural process, because the computer itself usually executes the program in a top-to-bottom sequential fashion. This one-dimensional format is fine for simple programs, but conditional branching and function calls may create complex behaviors that are not easily observed in a linear fashion. Flowcharts are one way to describe software in a two-dimensional format, specifically providing convenient mechanisms to visualize conditional branching and function calls. Flowcharts are very useful in the initial design stage of a software system to define complex algorithms. Furthermore, flowcharts can be used in the final documentation stage of a project, once the system is operational, in order to assist in its use or modification.

Where does one begin when learning a new skill? To me, I begin software development with the question, “What is it the program is supposed to do?” Next, I think of how I will test it. Testing is essentially a mechanism to see if the program does exactly what it is supposed to do, no more no less. Next, I list what are the inputs, and what are the outputs. Inside the system we have data. The data defines what do I know, so I define the data and give some examples. The software algorithm connects the inputs to the data, and software must connect the data to the outputs. Lastly, I test it. So you see I begin with testing and end with testing.

We will use flowcharts to illustrate what the software does (Figure 5.1).  The oval shapes define entry and exit points. The main entry point is the starting point of the software. Each function, or subroutine, also has an entry point. The exit point returns the flow of control back to the place from which the function was called. When the software runs continuously, as is typically the case in an embedded system, there will be no main exit point. We use rectangles to specify process blocks. In a high-level flowchart, a process block might involve many operations, but in a low-level flowchart, the exact operation is defined in the rectangle. The parallelogram will be used to define an input/output operation. Some flowchart artists use rectangles for both processes and input/output. Since input/output operations are an important part of embedded systems, we will use the parallelogram format, which will make it easier to identify input/output in our flowcharts. The diamond-shaped objects define a branch point or conditional block. Inside the diamond we can define what is being tested. Each arrow out of a condition block must be labeled with the condition causing flow to go in that direction. There must be at least two arrows out of a condition block, but there could be more than two. However, the condition for each arrow must be mutually exclusive (you can’t say “if I’m happy go left and if I’m tall go right” because it is unclear what you want the software to do if I’m happy and tall). Furthermore, the complete set of conditions must define all possibilities (you can’t say “if temperature is less than 20 go right and if the temperature is above 40 go left” because you have not defined what to do if the temperature is between 20 and 40). The rectangle with double lines on the side specifies a call to a predefined function. In this book, functions, subroutines, and procedures are terms that all refer to a well-defined section of code that performs a specific operation. Functions usually return a result parameter, while procedures usually do not. Functions and procedures are terms used when describing a high-level language, while subroutines are often used when describing assembly language. When a function (or subroutine or procedure) is called, the software execution path jumps to the function, the specific operation is performed, and the execution path returns to the point immediately after the function call. Circles are used as connectors. A connector with an arrow pointing out of the circle defines a label or a spot in the algorithm. There should be one label connector for each number. Connectors with an arrow pointing into the circle are jumps or goto commands. When the flow reaches a goto connector, the execution path jumps to the position specified by the corresponding label connector. It is bad style to use a lot of connectors.

Figure 5.1. Flowchart symbols.

 

There are a seemingly unlimited number of tasks one can perform on a computer, and the key to developing great products is to select the correct ones. Just like hiking through the woods, we need to develop guidelines (like maps and trails) to keep us from getting lost. One of the fundamentals when developing software, regardless whether it is a microcontroller with 1000 lines of assembly code or a large computer system with billions of lines of code, is to maintain a consistent structure. One such framework is called structured programming. C is a structured language, which means we begin with a small number of simple templates, as shown in Figure 5.2. A good high-level language will force the programmer to write structured programs. Structured programs in C are built from three basic templates: the sequence, the conditional, and the while-loop. At the lowest level, the “block” contains simple and well-defined commands, like Area = Height*Width; I/O functions are also low-level building blocks. To program in C, we combine existing structures into more complex structures. Each of the “blocks” in Figure 5.2 is either a simple well-defined command or another structure.

Figure 5.2. Flowchart showing the basic building blocks of structured programming.

 

Example 5.1: Using a flowchart describe the control algorithm that a toaster might use to cook toast. There will be a start button the user pushes to activate the machine. There is other input that measures toast temperature. The desired temperature is preprogrammed into the machine. The output is a heater, which can be on or off. The toast is automatically lowered into the oven when heat is applied and is ejected when the heat is turned off.

Solution: This example illustrates a common trait of an embedded system, that is, they perform the same set of tasks over and over forever. The program starts at main when power is applied, and the system behaves like a toaster until it is unplugged. Figure 5.3 shows a flowchart for one possible toaster algorithm. The system initially waits for the operator to push the start button. If the switch is not pressed, the system loops back reading and checking the switch over and over. After the start button is pressed, heat is turned on. When the toast temperature reaches the desired value, heat is turned off, and the process is repeated.

Figure 5.3. Flowchart illustrating the process of making toast.

 

Safety tip: When dealing with the potential for fire, you may want to add some safety features such as a time out or an independent check for temperature overflow.

Observation: The predefined functions in this chapter do not communicate any data between the calling routine and function. Data passed into a function are called input parameters, and data passed from the function back to the calling routine are called output parameters.

Observation: Notice in Figure 5.3 we defined a function Cook even though it was called from only one place. You might be tempted to think it would have been better to paste the code for the function into the one place it was called. There are many reasons it would be better to define the function as a separate software object: it will be easier to debug because there is a clear beginning and end of the function, it will make the overall system simpler to understand, and in the future we may wish to reuse this function for another purpose.

Interactive Tool 5.1

The following tool allows you to see the ordered sequence of steps (labeled A-H) executed as you interact with the Switch to give an input. When the code is executing in the Event handler subroutine you can click on the switch to turn it on and off and see the timeline (at the bottom) reflect this.


Main Event Event Return Output 0 to PortA1 Output 1 to PortA1 Input n from PA0 Input n from PA0 n n w w = 4 w = w-1 A B C D E F G H Cortex M4 Port A 6 PA0 PA1 3.3 V Microcontroller 0 >0 1 1 0 0 0 0 V 1600 Ω 10 kΩ Variables: w= n= No picture

Example 5.2. The system has one input and one output. An event should be recognized when the input goes from 0 to 1 and back to 0 again. The output is initially 0, but should go 1 after four events are detected. After this point, the output should remain 1. Design a flowchart to solve this problem.

Solution: This example also illustrates the concept of a subroutine. We break a complex system into smaller components so that the system is easier to understand and easier to test. In particular, once we know how to detect an event, we will encapsulate that process into a subroutine, called Event. In this example, the main program first sets the output to zero, calls the function Event four times, then it sets the output to one. To detect the 0 to 1 to 0 edges in the input, it first waits for 1, and then it waits for 0 again. The letters A through H in Figure 5.4 specify the software activities in this simple example. In this example, execution is sequential and predictable.

 

Figure 5.4. Flowchart illustrating the process waiting for four events.

 

 

 

 

Before we write software, we need to develop a plan. Software development is an iterative process. Even though we list steps the development process in a 1,2,3,4 order, in reality we cycle through these steps over and over. I like to begin with step 4), deciding how I will test it even before I decide what it does.

1) We begin with a list of the inputs and outputs. This usually defines what the overall system will do. We specify the range of values and their significance.

2) Next, we make a list of the required data. We must decide how the data is structured, what does it mean, how it is collected, and how it can be changed.

3) Next we develop the software algorithm, which is a sequence of operations we wish to execute. There are many approaches to describing the plan. Experienced programmers can develop the algorithm directly in C language. On the other hand, most of us need an abstractive method to document the desired sequence of actions. Flowcharts and pseudo code are two common descriptive formats. There are no formal rules regarding pseudo code, rather it is a shorthand for describing what to do and when to do it. We can place our pseudo code as documentation into the comment fields of our program. Next we write software to implement the algorithm as define in the flowchart and pseudo code.

4) The last stage is debugging. Learning debugging skills will greatly improve the quality of your software and the efficiency at which you can develop code. 

5.1 Background

We will use C in this class for two reasons. First, over the last ten years, it has ranked one or two out of all high-level languages. Second, C is by far the most common language for writing software for embedded systems.

Figure 5.5. Graph of popular programming languages over time.  http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html

Position
Jun 2013

Position
Jun 2012

Programming Language

Ratings
Jun 2013

Delta
Jun 2012

1

1

C

17.809%

+0.08%

2

2

Java

16.656%

+0.39%

3

4

Objective-C

10.356%

+1.26%

4

3

C++

8.819%

-0.54%

5

7

PHP

5.987%

+0.70%

6

5

C#

5.783%

-1.24%

7

6

(Visual) Basic

4.348%

-1.70%

8

8

Python

4.183%

+0.33%

9

9

Perl

2.273%

+0.05%

10

11

JavaScript

1.654%

+0.18%

11

10

Ruby

1.479%

-0.20%

12

12

Visual Basic .NET

1.067%

-0.15%

13

17

Transact-SQL

0.913%

+0.21%

14

14

Lisp

0.879%

-0.11%

15

16

Pascal

0.779%

-0.07%

16

21

Bash

0.711%

+0.09%

17

19

PL/SQL

0.657%

+0.02%

18

13

Delphi/Object Pascal

0.602%

-0.55%

19

18

Ada

0.575%

-0.11%

20

22

MATLAB

0.563%

0.00%

Table 5.1. Top 20 popular programming languages. http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html

                  

                     Video 5.1. History of the C Programming Language

C is a general-purpose programming language initially developed by Dennis Ritchie between 1969 and 1973 while at AT&T Bell Labs, see Figure 5.6. At the time, there were programming languages called A and another named B, so Ritchie decided to name his language C. Dennis Ritchie and Brian Kernighan wrote the first book on C, The C Programming Language.  Ritchie was also one of the developers of the Unix operating system.

 

Figure 5.6. Dennis MacAlistair Ritchie

As C became more popular, many derivative languages were introduced. C++ was developed by Bjarne Stroustrup 1979-1983 also at Bell Labs. C++ is a language originally called “C plus classes”. In 1999, a professional standard version of C, called C99, was defined. When you download Tivaware (http://www.ti.com/tool/sw-tm4c) from Texas Instruments, you will notice TI’s example code for the TM4C123 has been written in C99. In this class however, we will use the more simple C language.

A compiler is system software that converts a high-level language program (human readable format) into object code (machine readable format). It produces software that is fast but to change the software we need to edit the source code and recompile.

C code (z = x+y;) → Assembly code (ADD R2,R1,R0)  → Machine code (0xEB010200)

An assembler is system software that converts an assembly language program (human readable format) into object code (machine readable format).

Assembly code (ADD R2,R1,R0)  → Machine code (0xEB010200)

An interpreter executes directly the high level language. It is interactive but runs slower than compiled code. Many languages can be compiled or interpreted. The original BASIC (Beginner's All-purpose Symbolic Instruction Code) was interpreted. This means the user typed software to the computer, and the interpreter executed the commands as they were typed. In this class, an example of the interpreter will be the command window while running the debugger. For more information on this interpreter, run Keil uVision and execute Help->uVisionHelp. Next, you need to click the Contents tab, open the uVisionIDEusersGuide, and then click DebugCommands. It will show you a list of debugger commands you can type into the command window.

A linker builds software system by connecting (linking) software components. In Keil uVision, the build command (Project->BuildTarget) performs both a compilation and a linking. The example code in this module has three software components that are linked together. These components are

     startup.s
     uart.c
     main.c

A loader will place the object code in memory. In an embedded system, the loader will program object code into flash ROM. In Keil uVision, the download command (Flash->Download) performs a load operation.

A debugger is a set of hardware and software tools we use to verify system is operating correctly. The two important aspects of a good debugger are control and observability.

5.2 Structure and Organization of C

In assembly language, symbols placed at the beginning of each line have special meaning. On the contrary, C is a free field language. Except for preprocessor lines that begin with #, spaces, tabs and line breaks have the same meaning. This means we can place more than one statement on a single line, or place a single statement across multiple lines. We could write a function without any line breaks. Since we rarely make hardcopy printouts of our software, it is not necessary to minimize the number of line breaks. Furthermore, we could have added extra line breaks. I prefer the style of the program on the right because each line contains one complete thought or action. As you get more experienced, you will develop a programming style that is easy to understand. Although spaces, tabs, and line breaks are syntactically equivalent, their proper usage will have a profound impact on the readability of your software. The following three functions are identical; I like the third one.

unsigned long M=1;
unsigned long Random(void){M=1664525*M+1013904223;return(M);}

unsigned long M=1;
unsigned long

Random(void){
  M = 1664525*M
       +1013904223;
 return(M);
}

unsigned long M=1;
unsigned long Random(void){
  M = 1664525*M+1013904223;
  return(M);

}

Program 5.1. Three equivalent functions that return a random number.

Another situation where spaces, tabs and line breaks matter is string constants. We cannot type tabs or line breaks within a string constant. The characters between the first " and second " define the string constant. A string is a set of ASCII characters terminated with a 0. For example, the following C code will output my name:

                  printf("Jonathan Valvano");

 

The variable M, the function Random, the operation *, and the keyword long are tokens in C Each token must be contained on a single line. We see in the above example that tokens can be separated by white spaces, which include space, tab, line break, or by special characters. Special characters include punctuation marks (Table 5.2) and operations (Table 5.4).

 

Punctuation

Meaning

;

End of statement

:

Defines a label

,

Separates elements of a list

( )

Start and end of a parameter list

{ }

Start and stop of a compound statement

[ ]

Start and stop of a array index

" "

Start and stop of a string

' '

Start and stop of a character constant

Table 5.2. Special characters can be punctuation marks.

 

Punctuation marks (semicolons, colons, commas, apostrophes, quotation marks, braces, brackets, and parentheses) are very important in C. It is one of the most frequent sources of errors for both the beginning and experienced programmers.

There are four sections of a C program as shown in Program 5.2. The first section is the documentation section, which includes the purpose of the software, the authors, the date, and any copyright information. When the software involves external hardware we will add information about how the external hardware is connected. The second section is the preprocessor directives. We will use the preprocessor directive #include to connect this software with other modules. We use diamond braces to include system libraries, like the standard I/O, and we use quotes to link up with other user code within the project. In this case the uart module is software we wrote to perform I/O with the universal asynchronous receiver/transmitter (uart). We will discuss modular programming in great detail in this class. The third section is global declarations section. This section will include global variables and function prototypes for functions defined in this module. The last section will be the functions themselves. In this class we will use the terms subroutine, procedure, function, and program interchangeably. Every software system in C has exactly one main program, which define where it begins execution.

//**** 0. Documentation Section
//  This program calculates the area of square shaped rooms
//  Author: Ramesh Yerraballi & Jon Valvano
//  Date: 6/28/2013
//
// 1. Pre-processor Directives Section
#include <stdio.h>  // Diamond braces for sys lib: Standard I/O
#include "uart.h"   // Quotes for user lib: UART lib

// 2. Global Declarations section
// 3. Subroutines Section
// MAIN: Mandatory routine for a C program to be executable

int main(void) {
  UART_Init();  // call subroutine to initialize the uart
  printf("This program calculates areas of square-shaped rooms\n");
}

Program 5.2. Software to calculate the area of a square room.

                  

                     Video 5.2. Your first C Program (in Keil) from scratch

There are two types of comments. The first type explains how to use the software. These comments are usually placed at the top of the file, within the header file, or at the start of a function. The reader of these comments will be writing software that uses or calls these routines. The second type of comments assists a future programmer (ourselves included) in changing, debugging or extending these routines. We usually place these comments within the body of the functions. The comments on the right of each line are examples of the second type.

Preprocessor directives begin with # in the first column. As the name implies preprocessor commands are processed first. I.e., the compiler passes through the program handling the preprocessor directives. Although there are many possibilities (assembly language, conditional compilation, interrupt service routines), I thought I’d mention the two most important ones early in the class. We create a macro using #define  to define constants.

#define SIZE 10

Basically, wherever SIZE is found as a token, it is replaced with the 10. A second important directive is the #include, which allows you to include another entire file at that position within the program. The #include directive will include the file named tm4c123ge6pm.h at this point in the program. This file will define all the I/O port names for the TM4C123.

#include "tm4c123ge6pm.h"

 

5.3 Variables and Expressions

Variables are used to hold information. In C, we define a variable by specifying the name of the variable and the type. Table 5.3 lists the possible data types.

Data type

Precision

Range

unsigned char

8-bit unsigned

0 to +255

signed char

8-bit signed

-128 to +127

unsigned int

compiler-dependent

 

int

compiler-dependent

 

unsigned short

16-bit unsigned

0 to +65535

short

16-bit signed

-32768 to +32767

unsigned long

unsigned 32-bit

0 to 4294967295L

long

signed 32-bit

-2147483648L to 2147483647L

float

32-bit float

±10-38 to ±10+38 

double

64-bit float

±10-308 to ±10+308

Table 5.2. Data types in C.

 

On the Keil compiler, there is an option to specify whether char all by itself without a signed or unsigned before it is considered signed or unsigned. Keil considers int as 32 bits. In this class we will avoid int and use long for 32-bit variables so there is no confusion. We will assume char is signed, but it is good practice to see exactly how char and int are treated by your compiler.

Variables declared outside of a function, like M in Program 5.1, are properly called external variables because they are defined outside of any function. While this is the standard term for these variables, it is confusing because there is another class of external variable, one that exists in a separately compiled source file. In this document we will refer to variables in the present source file as globals, and we will refer to variables defined in another file as externals. There are two reasons to employ global variables. The first reason is data permanence. The other reason is information sharing. Normally we pass information from one module to another explicitly using input and output parameters, but there are applications like interrupt programming where this method is unavailable. For these situations, one module can store data into a global while another module can view it.

Local variables are very important in C programming. They contain temporary information that is accessible only within a narrow scope. We can define local variables at the start of a compound statement. We call these local variables since they are known only to the block in which they appear, and to subordinate blocks. The variables side and area in Program 5.3 are local. In C, local variable must be declared immediately after a brace { that begins a compound statement. Unlike globals, which are said to be static, locals are created dynamically when their block is entered, and they cease to exist when control leaves the block. Furthermore, local names supersede the names of globals and other locals declared at higher levels of nesting. Therefore, locals may be used freely without regard to the names of other variables. Although two global variables cannot use the same name, a local variable of one block can use the same name as a local variable in another block. Programming errors and confusion can be avoided by understanding these conventions. Local variables are implemented in registers or allocated on the stack.

Program 5.3 illustrates the assignment operator. Notice that in the line side=3; the side is on the left hand side of the = . The left side of the assignment specifies the address into which the data transfer will occur. On the other hand, if we were to wrote area=side; the side is on the right hand side of the = . The right side of an assignment statement will evaluate into a value, which specifies the data to be transferred. Notice that the line area=side; creates two copies of the data. The original value remains in side, while area also contains this value. As mentioned above, variables have a type (Table 5.3), and the expression on the right of an assignment statement must evaluate to a value of that same type. If side has the value 3, the expression side*side evaluates to a 9, and the 9 is stored into the variable area. The printf is used to output the results to the uart port.

int main(void) {
  unsigned long side; // room wall meters
  unsigned long area; // size squared meters
  UART_Init();        // call subroutine to initialize the uart
  side = 3;
  area = side*side;
  printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
}

Program 5.3. Simple program illustrating variables and assignment statements.

 

                  

                     Video 5.3. Variables and Assignment operation

style="background: white none repeat scroll 0%; -moz-background-clip: initial; -moz-background-origin: initial; -moz-background-inline-policy: initial;"> 

 

Program 5.4 illustrates the arithmetic operations of addition, subtraction, multiplication and division. In the operation x+4*y, multiplication has precedence over addition. Table 5.4 lists the operators available in the C language.

void main(void){
long x,y,z;    // Three local variables
   x=1; y=2;   // set the values of x and y
   z = x+4*y;  // arithmetic operation  
   x++;        // same as x=x+1;      
   y--;        // same as y=y-1;     
   x = y<<2;   // left shift same as x=4*y;      
   z = y>>2;   // right shift same as x=y/4;     
   y += 2;     // same as y=y+2;      
}

Program 5.4. Simple program illustrating C arithmetic operators.

 

Operation

Meaning

 

Operation

Meaning

=

Assignment statement

 

==

Equal to comparison

?

Selection

 

<=

Less than or equal to

Less than

 

>=

Greater than or equal to

Greater than

 

!=

Not equal to

!

Logical not (true to false, false to true)

 

<< 

Shift left

~

1’s complement

 

>> 

Shift right

+

Addition

 

++

Increment

-

Subtraction

 

--

Decrement

*

Multiply or pointer reference

 

&&

Boolean and

/

Divide

 

||

Boolean or

%

Modulo, division remainder

 

+=

Add value to

|

Logical or

 

-=

Subtract value to

&

Logical and, or address of

 

*=

Multiply value to

^

Logical exclusive or

 

/=

Divide value to

.

Used to access parts of a structure

 

|=

Or value to

 

 

 

&=

And value to

 

 

 

^=

Exclusive or value to

 

 

 

<<=

Shift value left

 

 

 

>>=

Shift value right

 

 

 

%=

Modulo divide value to

 

 

 

->

Pointer to a structure

Table 5.4. Special characters can be operators; operators can be made from 1, 2, or 3 characters.

 

As with all programming languages the order of the tokens is important. There are two issues to consider when evaluating complex statements. The precedence of the operator determines which operations are performed first. In expression z=x+4*y, the 4*y is performed first because * has higher precedence than + and =. The addition is performed second because + has higher precedence than =. The assignment = is performed last. Sometimes we use parentheses to clarify the meaning of the expression, even when they are not needed. Therefore, the line z=x+4*y; could have been written as z=(x+4*y); z=(x+4*y); or z=(x+(4*y));

The second issue is the associativity. Associativity determines the left to right or right to left order of evaluation when multiple operations of equal precedence are combined. For example + and - have the same precedence, so how do we evaluate the following?

          z = y-2+x;

We know that + and - associate the left to right, this function is the same as z=(y-2)+x;. Meaning the subtraction is performed first because it is more to the left than the addition. Most operations associate left to right, but the Table 5.5 illustrates that some operators associate right to left.

Observation: When confused about precedence (and aren't we all) add parentheses to clarify the expression.

 

Precedence

Operators

Associativity

Highest

() []. ->  ++(postfix)  --(postfix)

Left to right

 

++(prefix)  --(prefix)   !  ~ sizeof(type) +(unary)

 -(unary)  &(address)  *(dereference)

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

Lowest

,

Left to right

Table 5.5. Precedence and associativity determine the order of operation.

 

: Which C data type does one use for numbers in the range of 0 to 200?

: Which C data type does one use for numbers in the range of -10 to +10?

: Which C data type does one use for numbers in the range of -1000 to +1000?

: Which C data type does one use for numbers in the range of zero to a million?

: What is the range of values possible with a C data type of int?

: Add parentheses to clarify this expression   x&1&&y+1<z*4;

 

5.4 Functions

A function is a sequence of operations that can be invoked from other places within the software. We can pass zero or more parameters into a function. A function can have zero or one output parameter. It is important for the C programmer to distinguish the two terms declaration and definition. A function declaration specifies its name, its input parameters and its output parameter. Another name for a function declaration is prototype. A data structure declaration specifies its type and format. On the other hand, a function definition specifies the exact sequence of operations to execute when it is called. A function definition will generate object code, which are machine instructions to be loaded into memory that perform the intended operations. A data structure definition will reserve space in memory for it. The confusing part is that the definition will repeat the declaration specifications. The C compiler performs just one pass through the code, and we must declare data/functions before we can access/invoke them. To run, of course, all data and functions must be defined. A function to calculate the area of a square room is shown in Program 5.5. We can see that the declaration shows us how to use the function, not how the function works. Because the C compilation is a one-pass process, an object must be declared or defined before it can be used in a statement. Actually the preprocessor performs the first pass through the program that handles the preprocessor directives.  A top-down approach is to first declare a function, use the function, and lastly define the function as illustrated in Program 5.5.

unsigned long Calc_Area(unsigned long s);
int main(void) {
  unsigned long side; // room wall meters
  unsigned long area; // size squared meters
  UART_Init();  // call subroutine to initialize the uart
  printf("This program calculates areas of square-shaped rooms\n");
  side = 3;
  area = Calc_Area(side);
  printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
  side = side+2;
  area = Calc_Area(side);
  printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
}
// Calculates area
// Input:  side of a room (unsigned long) in meters
// Output: area of the room (unsigned long) in square meters
unsigned long Calc_Area(unsigned long s) {
  unsigned long result;
  result = s*s;
  return(result);
}

Program 5.5. A main program that calls a function. In this case the declaration occurs first.

A bottom-down approach is to first define a function, and then use the function as illustrated in Program 5.6. In the bottom up approach, the definition both declares its structure and defines what it does.

// Calculates area
// Input:  side of a room (unsigned long) in meters
// Output: area of the room (unsigned long) in square meters
unsigned long Calc_Area(unsigned long s) {
  unsigned long result;
  result = s*s;
  return(result);
}
int main(void) {
  unsigned long side; // room wall meters
  unsigned long area; // size squared meters
  UART_Init();  // call subroutine to initialize the uart
  printf("This program calculates areas of square-shaped rooms\n");
  side = 3;
  area = Calc_Area(side);
  printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
  side = side+2;
  area = Calc_Area(side);
  printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
}

Program 5.6. A main program that calls a function. In this case the definition occurs before its use.

 

                  

                     Video 5.4. Functions in C

 

Interactive Tool 5.2

The sum function (aka subroutine) in Program 5.7 has two 32-bit signed input parameters, and one 32-bit signed output parameter. The interesting part is that (in assembly) after the operations within the subroutine are performed, control returns to the place right after where the subroutine was called. It is the same in C. You will also see how the registers are manipulated as the code flows.

Assembly Code Registers
Address Machine Code Label Instruction Comment

0x00000660 EB010200 sum ADD R2,R1,R0 ;z=x+y
0x00000664 4610 MOV R0,R2 ;return value
0x00000666 4770 BX LR
0x00000668 F44F60FA main MOV R0,#2000 ;first parameter
0x0000066C F44F61FA MOV R1,#2000 ;second parameter
0x00000670 F7FFFFF6 BL sum ;call function
0x00000674 4603 MOV R3,R0 ;a=sum(2000,2000)
0x00000676 F04F0400 MOV R4,#0x00 ;b=0
0x0000067A 4620 loop MOV R0,R4 ;first parameter
0x0000067C F04F0101 MOV R1,#0x01 ;second parameter
0x00000680 F7FFFFEE BL sum ;call function
0x00000684 4604 MOV R4,R0 ;b=sum(b,1)
0x00000686 E7F8 B loop
Register Value
R0 0x00000000
R1 0x00000000
R2 0x00000000
R3 0x00000000
R4 0x00000000
R5 0x00000000
R6 0x00000000
R7 0x00000000
R8 0x00000000
R9 0x00000000
R10 0x00000000
R11 0x00000000
R12 0x00000000
R13(SP) 0x00000000
R14(LR) 0x00000000
R15(PC) 0x00000668
C Code
long sum(long x, long y){ long z;
z = x+y;
return(z);
} void main(void){ long a,b;
a = sum(2000,2000);
b = 0;
while(1){
b = sum(b,1);
}
}

Program 5.7. A function with two inputs and one output.


To specify the absence of a parameter we use the expression void. The body of a function consists of a statement that performs the work. Normally the body is a compound statement between a {} pair. If the function has a return parameter, then all exit points must specify what to return.

: What does it mean to say a function as one input parameter?

: What does it mean to say a function as one output parameter?

 

5.5 Conditional branching and loops

In Program 5.8 we will introduce a simple conditional control structure. Assume the global variable error is initialized to zero. The goal is to make sure the function is being used properly. An effective software design approach is to test the input parameters of a function to make sure the values make sense. An unsigned long can represent a number up to 4 billion. Clearly the system is not operating properly if we are trying to calculate the size of a room with 4 billion meter sides. In this case, we define the largest possible room to have a side of 25 meters.  The expression s<=25 will return true if the side is less than or equal to 25 and will return a false if the side is strictly greater than 25. The statement immediately following the if will be executed if the condition is true. The statement immediately following the else will be executed if the condition is false.

 

unsigned long error;
// Calculates area
// Input:  side of a room (unsigned long)
// Output: area of the room (unsigned long)
// Notes: ...
unsigned long Calc_Area(unsigned long s) {
  unsigned long result;
  if(s <= 25){
    result = s*s;
  }else{
    result = 0; // mistake
    error = error +1;
  }
  return(result);
}

Program 5.8. Simple program illustrating the C if else control structure.

The goal in program 5.9 is to test the Calc_Area function. Like the if statement, the while statement has a conditional test (i.e., returns a true/false). The statement immediately following the while will be executed over and over until the conditional test becomes false.

int main(void) {
  unsigned long side; // room wall meters
  unsigned long area; // size squared meters
  UART_Init();  // call subroutine to initialize the uart
  printf("This program calculates areas of square-shaped rooms\n");
  side = 1;
  while(side < 50){
    area = Calc_Area(side);
    printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
    side = side+1;
  }
}

Program 5.9. Simple program illustrating the C while control structure.

The for control structure has three parts and a body.

     for(part1;part2;part3){body;}

In Program 5.10, the first part side=1 is executed once at the beginning. Before the body is executed, the end-condition part 2 is executed. If the condition is true, side<50 then the body is executed. After the body is executed, the third part is executed, side=side+1. The second part is always a conditional that results in a true or a false. The body and third part are repeated until the conditional is false.

int main(void) {
  unsigned long side; // room wall meters
  unsigned long area; // size squared meters
  UART_Init();  // call subroutine to initialize the uart
  printf("This program calculates areas of square-shaped rooms\n");
  for(side = 1; side < 50; side = side+1){
    area = Calc_Area(side);
    printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
  }
}

Program 5.10. Simple program illustrating the C for-loop control structure.

                  

                     Video 5.5. if-then conditional and while loop

Although C is a free field language, notice how the indenting has been added to programs in this book. The purpose of this indenting is to make the program easier to read. On the other hand since C is a free field language, the following two statements are quite different

 

if(n1>100) n2=100; n3=0;
if(n1>100) {n2=100; n3=0;}

 

In both cases n2=100; is executed if n1>100. In the first case the statement n3=0; is always executed, while in the second case n3=0; is executed only if n1>100.

 

5.6 Keyboard input using scanf

 //**** 0. Documentation Section
//  This program calculates the area of square shaped rooms
//  Author: Ramesh Yerraballi & Jon Valvano
//  Date: 6/28/2013
//
// 1. Pre-processor Directives Section
#include <stdio.h>  // Diamond braces for sys lib: Standard I/O
#include "uart.h"   // Quotes for user lib: UART lib
// 2. Global Declarations section
// global variable
unsigned long error;
// Function Prototypes
// Compiler aid for "type checking"
void Initialize(void);
unsigned long Calc_Area(unsigned long s); // Says Calc_Area expects
            // an unsigned long and returns an unsigned long       
// 3. Subroutines Section
// MAIN: Mandatory routine for a C program to be executable
int main(void) {
  unsigned long side; // room wall meters
  unsigned long area; // size squared meters
  UART_Init();  // call subroutine to initialize the uart
  Initialize(); 
  printf("This program calculates areas of square-shaped rooms\n");
  while(side != 0){
    printf("Give room side(zero to quit):");
    scanf("%ld", &side);
    area = Calc_Area(side);
    if(area != 0){
      printf("\nArea with side of %ld m is %ld sqr m\n",side,area);
    } else {
      printf("\n Size cannot exceed 25 meters\n");
    }
  }
  printf("Goodbye (Mistake count = %ld\n", error);
}
// Initialize global
// Inputs: none
// Outputs: none
// Notes:
void Initialize(void){
  error = 0;
}
// Calculates area
// Input:  side of a room (unsigned long)
// Output: area of the room (unsigned long)
// Notes: ...
unsigned long Calc_Area(unsigned long s) {
  unsigned long result;
  if(s <= 25){
    result = s*s;
  }else{
    result = 0; // mistake
    error = error +1;
  }
  return(result);
 
}
Program 5.11. Software to calculate the area of a square room.

                  

                     Video 5.6. scanf for getting user input and Debugging

5.7 C Keywords and Punctuation

C has predefined tokens, called keywords, which have specific meaning in C programs, as listed in Table 5.6. This section describes keywords and punctuation.

Keyword

Meaning

__asm

Specify a function is written in assembly code (specific to ARM Keil™ uVision®)

auto

Specifies a variable as automatic (created on the stack)

break

Causes the program control structure to finish

case

One possibility within a switch statement

char

Defines a number with a precision of 8 bits

const

Defines parameter as constant in ROM, and defines a local parameter as fixed value

continue

Causes the program to go to beginning of loop

default

Used in switch statement for all other cases

do

Used for creating program loops

double

Specifies variable as double precision floating point

else

Alternative part of a conditional

extern

Defined in another module

float

Specifies variable as single precision floating point

for

Used for creating program loops

goto

Causes program to jump to specified location

if

Conditional control structure

int

Defines a number with a precision that will vary from compiler to compiler

long

Defines a number with a precision of 32 bits

register

Specifies how to implement a local

return

Leave function

short

Defines a number with a precision of 16 bits

signed

Specifies variable as signed (default)

sizeof

Built-in function returns the size of an object

static

Stored permanently in memory, accessed locally

struct

Used for creating data structures

switch

Complex conditional control structure

typedef

Used to create new data types

unsigned

Always greater than or equal to zero

void

Used in parameter list to mean no parameter

volatile

Can change implicitly outside the direct action of the software.

while

Used for creating program loops

 Table 5.6. Keywords have predefined meanings.

The volatile keyword disables compiler optimization, forcing the compiler to fetch a new value each time. We will use volatile when defining I/O ports because the value of ports can change outside of software action. We will also use volatile when sharing a global variable between the main program and an interrupt service routine. It is a good programming practice not to use these keywords for your variable or function names.

Punctuation marks are very important in C. It is one of the most frequent sources of errors for both beginning and experienced programmers.

Semicolons are used as statement terminators. Strange and confusing syntax errors may be generated when you forget a semicolon, so this is one of the first things to check when trying to remove syntax errors. Notice that one semicolon is placed at the end of every simple statement in Program 5.12. When executed, the function Step will output the pattern 10, 9, 5, 6 to Port D. The #define statement creates a substitution rule, such that every instance of STEPPER in the program is replaces with  (*((volatile unsigned long *)0x4000703C)).

#define STEPPER (*((volatile unsigned long *)0x4000703C))

void Step(void){

  STEPPER = 10;

  STEPPER = 9;

  STEPPER = 5;

  STEPPER = 6;

}

Program 5.12. Semicolons are used to separate one statement from the next.

Preprocessor directives do not end with a semicolon since they are not actually part of the C language proper. Preprocessor directives begin in the first column with the # and conclude at the end of the line. Program 5.13 will fill the array DataBuffer with data read from the input Port A. We assume in this example that Port A has been initialized as an input. Notice that semicolons are used to separate the three fields of the for loop statement.

unsigned char DataBuffer[100];

#define GPIO_PORTA_DATA_R  (*((volatile unsigned long *)0x400043FC))

void Fill(void){ long j;

  for(j=0; j<100; j++){

    DataBuffer[j] = GPIO_PORTA_DATA_R;

  }

}

Program 5.13. Semicolons are used to separate three fields of the for statement.

Colons terminate case, and default prefixes that appear in switch statements. In Program 5.14 one output to the stepper motor produced each time the function OneStep is called. The proper stepper motor sequence is 10–9–5–6. The default case is used to restart the pattern. For both applications of the colon (goto and switch), we see that a label is created that is a potential target for a transfer of control. Notice the use of colons in Program 5.14.

unsigned char Last=10;

void OneStep(void){

unsigned char theNext;

  switch(Last){

    case 10: theNext = 9; break;   // 10 to 9

    case 9: theNext = 5;  break;   // 9 to 5

    case 5: theNext = 6;  break;   // 5 to 6

    case 6: theNext = 10; break;   // 6 to 10

    default: theNext = 10;

  }

  GPIO_PORTD_DATA_R = theNext;

  Last = theNext;   // set up for next call

}

Program 5.14. Colons are also used with the switch statement.

Commas separate items that appear in lists. We can create multiple variables of the same type using commas.

unsigned short beginTime,endTime,elapsedTime;

Lists are also used with functions having multiple parameters, both when the function is defined and called. Program 5.15 adds two 16-bit signed numbers, implementing ceiling and floor. Notice the use of commas in Program 5.15.

short add(short x, short y){ short z;

  z = x+y;

  if((x>0)&&(y>0)&&(z<0))z = 32767;

  if((x<0)&&(y<0)&&(z>0))z = -32768;

  return(z);

}

void main(void){ short a,b;

  a = add(2000,2000)

  b = 0

  while(1){

    b = add(b,1);

}

Program 5.15. Commas separate the parameters of a function.

Lists can also be used in general expressions. Sometimes it adds clarity to a program if related variables are modified at the same place. The value of a list of expressions is always the value of the last expression in the list. In the following example, first thetime is incremented, next thedate is decremented, and then x is set to k+2.

X = (thetime++, thedate--, k+2);

Apostrophes are used to specify character literals. Assuming the function OutChar will display a single ASCII character, Program 5.16 will display the lower case alphabet.

void Alphabet(void){ unsigned char mych;

  for(mych='a'; mych<='z'; mych++){

    OutChar(mych); // Print next letter

  }     

}

Program 5.16. Apostrophes are used to specify characters.

Quotation marks are used to specify string literals. Strings are stored as a sequence of ASCII characters followed by a termination code, 0. Program 5.17 will display “Hello World” twice.

const unsigned char Msg[]= "Hello World"; // string constant

void OutString(const unsigned char str[]){ int i;

  i = 0;

  while(str[i]){     // output until the 0 termination

    OutChar(str[i]); // Print next letter

    i = i+1;

  }

}

void PrintHelloWorld(void){

  OutString("Hello World");

  OutString(Msg);

}

Program 5.17. Quotation marks are used to specify strings.

Braces {} are used throughout C programs. The most common application is for creating a compound statement. Each open brace { must be matched with a closing brace }. Notice the use of indenting helps to match up braces. Each time an open brace is used, the source code is tabbed over using 2 spaces. In this way, it is easy to see at a glance the brace pairs.

Square brackets enclose array dimensions (in declarations) and subscripts (in expressions).

Parentheses enclose argument lists that are associated with function declarations and calls. They are required even if there are no arguments. As with all programming languages, C uses parentheses to control the order in which expressions are evaluated. Thus, (11+3)/2 yields 7, whereas 11+3/2 yields 12. Parentheses are very important when writing expressions.

 

5.8. Chapter 5 Quiz

 

5.1 Match the punctuation with its meaning

;    sEnd of statement

:    Defines a label

,    Separates elements of a list

( )  Start and end of a parameter list

{ }  Start and stop of a compound statement

[ ]  Start and stop of a array index

" "  Start and stop of a string

' '  Start and stop of a character constant

 

5.2 Match the variable type with its minimum and maximum value

Data type

Range

unsigned char

0 to +255

signed char

-128 to +127

unsigned short

0 to +65535

short

-32768 to +32767

unsigned long

0 to 4294967295L

long

-2147483648L to 2147483647L

5.3 Match the variable type with its precision

Data type

Precision

char

8 bits

short

16 bits

long

32 bits

5.4 Are the variables side and area local or global?  

int main(void) {
  unsigned long side; // room wall meters
  unsigned long area; // size squared meters
  UART_Init();        // call subroutine to initialize the uart
  side = 3;
  area = side*side;
  printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
}

 

5.5 The goal is to return true if and only if the input is a number between 0x30 and 0x39.   

long CheckInput(long input) {
  if(( Input
xxx 0x30) yyy (Input zzz 0x39)){

    return 1;

  } else{

    return 0; // false

}

a) What C code do you need to place in the xxx position of the program?  

b) What C code do you need to place in the yyy position of the program?  

c) What C code do you need to place in the zzz position of the program?  

Reprinted with approval from Embedded Systems: Introduction to ARM Cortex-M Microcontrollers, 2013, ISBN: 978-1477508992, http://users.ece.utexas.edu/~valvano/arm/outline1.htm

 

Creative Commons License
Embedded Systems - Shape the World by Jonathan Valvano and Ramesh Yerraballi is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
Based on a work at http://users.ece.utexas.edu/~valvano/arm/outline1.htm.