© Armstrong Subero 2018

Armstrong Subero, Programming PIC Microcontrollers with XC8, https://doi.org/10.1007/978-1-4842-3273-6_2

2. The C Programming Language

Armstrong Subero

(1)Moruga, Trinidad and Tobago

C

C. The language we all love to hate. We’ve seen C++, Java, C#, Go, Python, and a host of other languages come and go. Yet C remains. C was, is, and will be. Assembly used to be the language of choice for programming 8-bit microcontrollers. However, the newer PIC® microcontrollers have a C-optimized architecture and a lot of memory. Assembly does have its place and it is still sometimes needed to optimize code. C programs typically occupy more code space than an equivalent Assembly one. In this book, I make an effort to maintain KISS principles; therefore, we use the simpler solution, which is C. So let’s look at some basic C.

This chapter can be skipped if you are a C programming guru, for anyone else though, it’s best you read this chapter.

C Programming

The C programming language was designed at a time when computing power was at a fraction of what it is today. Forget gigabytes of memory, there were kilobytes. Forget gigahertz of computing speed, we are talking megahertz here. Its sounds familiar, doesn’t it? Kilobytes of memory and a speed measured in megahertz. In fact, it sounds like a PIC® microcontroller, does it not? That’s essentially why C is ideal for programming microcontrollers.

C Program Structure

C programs have a structure they must follow. Listing 2-1 is a typical C program.

Listing 2-1 Typical C Program
/* This is a comment that usually contains information such as the name of the author, date and program name and details */

// This is where files are included and preprocessor directives take place

#include <stdio.h> // This is an example of a header file

// This is the main function, all C programs have a main function and
// This is where program execution begins


int main(void)
{ // opening parentheses very important
printf("Hello World!"); // some code is executed


return 0; // no code is executed after a return statement
} // closing parentheses

As you see, the typical C program has a comment block, include files, and a main function. As we examine further programs, you will notice that this template is maintained.

Comments

Comments are used in your program to describe lines of code. Comments are very useful because they help you remember what your code does. Comments can be used as needed, as they are completely ignored by the compiler. The C language uses comments that can be written using forward slashes and asterisks (see Listing 2-2).

Listing 2-2 Comments Using Forward Slashes and Asterisks
/* This is a comment
that can be used
to span multiple lines */

Comments can also be written with two forward slashes and these occupy one line (see Listing 2-3).

Listing 2-3 Comments Using Two Forward Slashes
// This comment occupies one line

Variables and Constants

A variable in C is a name used to refer to some location in memory and allows the programmer to assign a value to that location. Variables can be changed during the execution of a program unless they are explicitly declared as constants. Variables must be declared before they are used in a program (see Listing 2-4).

Listing 2-4 Declaring a Variable
// This is a variable being declared
int A;

After being declared, a variable can be initialized, that is to say, it can have an initial value assigned to it (see Listing 2-5).

Listing 2-5 Initializing a Variable
// This is a variable having a value assigned
A = 10;

Variables can also be declared and initialized at the same time (see Listing 2-6).

Listing 2-6 Declaring and Initializing a Variable
// This is a variable being declared and initialized at the same time.
int A = 10;

In the C language, variables may be of different types. For example, the variables used to store letters are different from the one used to store numbers. Table 2-1 shows some of the common variable types available in the C language.

Table 2-1 Common variables

Variable

Definition

Example

char

This variable type is used to store single characters.

1 char A = 'A'

int

This variable type stores integers.

1 int A = 1;

float

A float is used to store decimal numbers and usually has up to 23 significant figures.

1 float A = 6.1234567890...23

double

These are used to store decimal numbers and usually have up to 52 significant figures.

1 double A = 01234567890123456789...52

Constants

Constants can be thought of as special variables where the value cannot be changed. Constants are declared using the const keyword.

1 const int A = 10;

Arrays, Pointers, and Structures

Arrays

In C, an array is a type of data structure that can store several items, known as elements, of the same array. Arrays are very useful in C and embedded programming. The use of an array is fundamental when working with PIC® microcontrollers. An array can be declared with or without a specified size.

Example:

int temperatures[5] = {29, 25, 26, 25, 28};

The same sequence can be written as follows:

int temperatures[] = {29, 25, 26, 25, 28};

Elements of an array are accessed with the first element having an index of 0. These elements are accessed as follows:

int monday = temperature[0]; // monday will have value 29

You may also assign a value to a single element of an array using the following:

temperatures[2] = 27; // element 2 now has the value of 27

Pointers

The pointer. Many people have difficulty grasping the simple proposition. Pointers are powerful. Although I sparingly use pointers in this book, they are one of the most important C concepts.

Most people are confused as to what a pointer is and its purpose. A pointer is just another variable. Think of pointers like an int, char, or float. We know that an int stores numbers, char stores individual characters, and float stores decimal numbers. However, don’t let the asterisk and ampersand scare you.

A pointer simply is a variable that stores the memory address of another variable. It is quite simple to understand. Listing 2-7 shows an example of how to declare a pointer and common assignment.

Listing 2-7 Declaring a Pointer
// an integer declaration
int num;


// a pointer to an integer
int *num_pointer;


// the pointer now has memory address of the integer 'num'
num_pointer = &num;

See how simple pointers are to use? If you still have difficulty understanding them, then do some further research. There are entire books dedicated to the use of pointers.

Structures

You can get by with most simple C programming just by using arrays. However, when you want to store several data types, you must use a structure.

The struct keyword is used to declare structures. Listing 2-8 shows how you declare a structure.

Listing 2-8 Declaring a Structure
struct Speed{
byte slow;
byte normal;
byte fast;
};

To use a struct, you declare a type of the struct (see Listing 2-9).

Listing 2-9 Declaring a Type of Structure
// Declare MotorSpeed of type speed
struct Speed Motorspeed;

You then access a member of a structure with a dot (.) as the member access operator as follows:

MotorSpeed.slow = 10;

structs are very useful when programming microcontrollers.

Operators

Mathematics and logic are what a CPU thrives on. In C, there are many symbols that allow the microcontroller to perform logical and mathematical functions. I briefly go over them in Listings 2-10 through Listing 2-12.

Listing 2-10 Examples of Arithmetic Operators
// Addition operation adds operands
X + Y;


// Subtraction operation subtracts operands
X - Y;


// Multiplication multiples operands
X * Y;


// Division divides operands
X / Y;


// Modulus finds remainder after division
X % Y;


// Increment increases value by one
X++;


// Decrement decreases value by one
Y--;
Listing 2-11 Examples of Relational Operators
// Checks for equality
X == Y;


// Checks that values are not equal
X != Y;


// Determines if first operand is greater than the second
X > Y;


// Determines if first operand is less than the second
X < Y;


// Checks if the left operand is greater than or equal to the right one
X >= Y;


// Checks of the left operand is less than or equal to the right one
X <= Y;
Listing 2-12 Examples of Logical Operators
// Logical AND operator
X && Y;


// Logical OR operator
X || Y;


// Logical NOT operator
!(X)

Controlling Program Flow

if Statement

The if statement is used to make decisions within a program (see Listing 2-13).

Listing 2-13 Example of an if Statement
if (speed == 200)
{
turnLightOn();
}

else Statement

The else statement allows the programmer to perform another action and is a complement to the if statement (see Listing 2-14).

Listing 2-14 Example of an else Statement
if(speed == 200)
{
turnLightOn();
}


else
{
keepLightOff();
}

else if Statement

Sometimes we need to test for more than two conditions of the program and that is when we use an else if statement (see Listing 2-15).

Listing 2-15 Example of an else if Statement
if(speed == 200)
{
turnRedLightOn();
}


else if (speed == 150)
{
turnYellowLightOn();
}


else
{


}

switch statement

The switch statement is used when we need to compare a variable against different values (see Listing 2-16). It is used in situations where excessive if and else if statements would have been used. You must remember to include break statements within the cases; otherwise, the flow would fall to subsequent cases until a break statement. The default case is used when none of the other cases is true.

Listing 2-16 Example of a switch Statement
switch (speed)
{
case 100:
beepOneTime();
break;


case 150:
beepTwoTimes();
break;


case 200:

case 250:
turnOffEngine();
break;


break:
keepEngineRunning();
}

for Loop

The for loop is used when you need to execute a sequence of statements a number of times (see Listing 2-17).

Listing 2-17 Example of a for Loop
for(int x = 0; x<= 10; x++)
{
spi_send(0x01);


delay_ms(1000);
}

while Loop

The while loop repeats a group of statements while the condition specified is true (see Listing 2-18). while loops are very important in embedded systems and are typically used to create an infinite loop since there is usually no operating system to keep the program running. All the programs in this book utilize an infinite while loop.

Listing 2-18 Example of a while Loop
while(1)
{
readSensor();
checkBattery();
updateDisplay();
}

do Loop

The do loop works just like the while loop, except that it checks the conditions of the loop after execution and it will execute at least once. See Listing 2-19.

Listing 2-19 Example of do Loop
do
{
temp = readTemperature();
} while(temp < 40);

break Statement

The break statement is used to terminate a loop and, when it’s used, the statement immediately following the loop is executed (see Listing 2-20).

Listing 2-20 Example of a break Statement
if (temperature > 35)
{
break;
}

continue Statement

The continue statement causes a skip in the rest of the current iteration of the loop to take place (see Listing 2-21).

Listing 2-21 Example of a continue Statement
for (i = 0; i < 1023; i++)
{
if (i > 100 && i < 400)
{
continue;
}


spi_send(0x02);
}

goto Statement

The goto statement is looked upon with shame. However, there are instances when an unconditional jump is useful. Although using the goto statement often leads to spaghetti code, it is useful to understand how it operates.

The goto statement simply transfers the program flow to a point in the program that has a label (see Listing 2-22). The one time it may be necessary to use a goto loop is when there are deeply nested for loops or if statements.

Listing 2-22 Example of a goto Statement
myLabel:
turnOnSomething();


goto myLabel;

Preprocessor Directives

Before we discuss preprocessor directives, let’s take some time to think a little about IDEs and compilers. The IDE (Integrated Development Environment) is basically a program just like your text editor, browser, or video game. The difference is that the IDE program has a special purpose. It contains everything you need to develop the program that will run on your microcontroller. That means it consists of various parts, such as a code editor where you type your code, a debugger that helps you look for errors in your code, and a lot of other things that simplify the whole process of development.

One such part in the IDE is the compiler. A compiler converts your code (in this case written in C) into instructions that the microcontroller will understand. When this code is compiled, it is converted into something called an object file. After this step, basically a component called the linker takes these object files and converts them into the final file that will be executed by your microcontroller. There may be other steps in this process of generating the final hex file (program to be written to the microcontroller), but this is all you need to know.

Now we can understand what a preprocessor directive is. The preprocessor is another part of the IDE that uses directives, which cause the C program to be edited prior to compilation.

These preprocessor directives begin with a hash tag symbol. In XC8, you will encounter preprocessor directives a lot, especially with libraries that are designed to target more than one chip.

#define

The #define directive is the first we will look at. The #define statement in C defines macros. This statement is used a lot in embedded programming and is very useful. Instead of having to keep typing some constant, it is easier to use the #define directive. This is also useful in instances where constants may need to be changed.

For example, if we are writing a driver for an LCD that comes in two compatible variants—128x64 and 128x32—then instead of having to constantly write those numbers, since the dimensions of the LCD would remain the same, it is easier to write it as shown in Listing 2-23.

Listing 2-23 Define Macros Using #define
#define LCD_HEIGHT 128
#define LCD_WIDTH 64

A little warning though: Remember to omit the semicolon after the macro as it will generate compiler errors. Another important use of the #define directive is in the creation of function-like macros. These are macros that can be used to create a small “function” and are useful for the creation of small functions that may appear many times in your code. See Listing 2-24.

Listing 2-24 Example of #define Statement
#define MAX(x, y) ((X) > (Y) ? (X) : (Y))

The most important use of such functions I have found in practice is that they do not require a specific type and can use any generic type. In the example in Listing 2-24, it doesn’t matter if the parameters are int, float, or double, the maximum would still be returned. Learning to use the #define directive as it is very important. Sometimes you may see this referred to as a lambda function.

#if, #ifdef, #ifndef, #elif, and #else

These preprocessor directives are used for conditional compilation in the program. These directives are important. These directives are commonly used for debugging and to develop libraries that target multiple chips. They are straightforward. Listing 2-25 shows how the directives are used.

Listing 2-25 Examples of Preprocessor Directives in Use
#ifdef PIC16F1717
#define SPEED 200
#elif defined (__PIC24F__)
#define SPEED 300
#else
#define SPEED 100
#endif

Note that the conditional directives must end with an #endif statement.

#pragma

This is a C directive that in general-purpose programming is used for machine- or operating system-specific directives. This directive is most commonly encountered to set the configuration bits of the PIC® microcontroller (see Listing 2-26).

Listing 2-26 Example of #pragma
#pragma config PLLDIV = 2                  

Assembly vs. C

There are people who think Assembly is better for 8-bit microcontroller design. This may have been the case several years ago, but now that microcontrollers have a C optimized architecture, the need to have handwritten Assembly is less important now than it was before. The only case in which you may use Assembly is if you need to generate efficient code in the free version of the XC8 compiler, you have a chip in a legacy design that can only use Assembly, or of course you want to learn the architecture of the microcontroller on a deeper level. However, in this book I omit the use of Assembly.

Conclusion

This chapter contained a basic overview of the C programming language. With just the concepts presented here, you can do a lot, as we covered the most important keywords for our purposes. However, simply knowing the keywords to a language does not help you master it. It takes practice. If you are not proficient in the C language, I encourage you to find books and Internet resources to help you in your journey with the C programming language. If you are completely new to programming in general, I recommend you learn the basics. The book I personally recommend is Beginning C, 5 th Edition by Ivor Horton, available from Apress®. There are also many free resources on the web that teach complete beginner programming concepts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.135.81