Tuesday, December 2, 2014

Creating and Using Functions

All C programs run on functions, which are blocks of instructions used to execute a sequence of events. main ( ) is a C program's main function (hence the name), and is included in every program.  Other examples of typical functions include while (1) and for (xxxx), among others.  Every C program has at least one function, but most programs are divided up into several functions.  The division of these functions is up to the programmer, but logically they would be divided so that each function performs a specific task.   As the programmer, you can create your own functions for specific tasks aside from the typical functions which can be called throughout your program.

BASIC FUNCTION STRUCTURE

When creating your own functions you must first define the function ahead of time so that the compiler knows what we want when we call the function.  Think of this as learning a new language. If someone says a word you don't know, you won't understand the meaning.  The compiler reacts the same way.  Defining the function ahead of time is called a function declaration, which tells the compiler about the function's name, return type, and parameters.  The function declaration also provides the actual body of the function, or list of instructions the function performs.  The general form of a function is as follows:

return_type function_name (parameter list)
{
body of function
}

Return Type: A function may return a value, and the return_type specifies the data type to be returned such as "char" or "long".  Many functions return no value, so the return type would be declared as "void" to specify what basically equates to "expect nothing".
Function Name:  This is the actual name we give to our function, just like naming a variable.  The name of the function is what we use throughout the program when we want to call the function.
Parameters:  A function may have values passed into it.  An example of this would be if the function uses a variable, then we can place a global variable, or define a local variable within the parameter list.  You may pass more than one parameter to a function by separating them with commas, such as function_name (variable1, variable2).  Also, just like return type, many functions require no parameters and we would again use the C keyword "void" within the parenthesis.
Function Body:  This is where the function's instructions reside. This is what defines what the function actually does.

The macro __delay_ms(x) is an example of a function.  To see the body of this function, we must look at a header file.  Remember how our programs include the xc.h file?  This points the compiler to the xc.h header file which includes a bunch of definitions, functions, and macros the compiler needs in order to generate the program code for common and basic tasks.  Figure 1 shows the contents of the xc.h file.  These files open with the window's notepad program and are located in the program files> microchip> xc8> version#> include directory.
Figure 1.
The contents of the xc.h header file basically define the file as _XC_H_, and say that if XC8 is our compiler (which it is) then include the "htc.h" header file.  Basically, this file does nothing more than point to another header file.  What is the point then, you might ask? The xc.h file is a top-level file that points the compiler to everything else it needs without the programmer (you and me) having to include it directly.

The htc.h header file defines some common definitions and some debugger stuff, but most importantly it includes another header file depending on what compiler we're using.  Microchip used to have a C compiler known as PicC, and for some reason still refer to XC8 as PICC within this header file. We can see that the htc.h header file points us next to the pic.h header file, since it says #if defined(__PICC__) #include <pic.h>

The pic.h header file is where a lot of definitions and macros reside, as well as some important functions.  Let's look at the section that includes the __delay routines (Figure 2).


Figure 2.
 #ifdef __PICC__ states that if XC8 (PICC) is defined as our compiler, include the following statements.
Look at the comment "NOTE: To use the macros below, YOU must have previously defined _XTAL_FREQ".  See why we have been including that definition?
#define is where these macros are defined, and we can see the use of parameters here.  The macro __delay_us(x) is defined with the name "__delay_us" and includes a parameter being passed to the function.  The parameter in this case is "x".  The instruction that is executed in place of "__delay_us(x)" then follows, which is "_delay((unsigned long)((x)*(_XTAL_FREQ/4000000.0))).  This is an example also of the compiler doing math.

The compiler accepts the instruction "_delay(x)" which is a delay for x number of instruction cycles.  The microcontroller performs one instruction for every 4 clock cycles (see the blog post about The Microcontroller Oscillator).  One clock cycle is our oscillator frequency, so one instruction is executed every (frequency/4).  One microsecond, denoted "us" is equal to 0.000001 seconds, or one one-millionth of a second.  Therefore, the statement "_delay((unsigned long)((x)*(XTAL_FREQ/4000000.0))) says that delay(for this many clock cycles) where the parameter is a mathematical formula.  If we ask the compiler to delay for 40 microseconds by writing the statement "__delay_us(40)" using a 4MHz crystal frequency, the compiler does the following things:
Replaces XTAL_FREQ with our defined number, which is 4000000 Hertz.
Replaces x with our requested 40.
Defines an unsigned long integer to hold the result.
Divides XTAL_FREQ (4000000) by 4000000.0, where the result in this case is 1.
Multiples x (40) by the previous result, which results in 40.
The unsigned long integer becomes 40.
The microcontroller delays for 40 instruction cycles, where each cycle is equal to 1 microsecond.

When we write the statement "__delay_ms(100)" we are asking the PIC to delay for 100 milliseconds. One millisecond is equal to 0.001 seconds.  Using the same XTAL_FREQ, the math that occurs is:
((100)*(4000000/4000.0))
(100)*(1000)
100,000
Thus the PIC delays for 100,000 instruction cycles resulting in a delay of 100 milliseconds.  This math can be checked in reverse since we know that 1 instruction is executed every 1/1000000th of a second.  If we are delayed by 100,000 instruction cycles and 1 instruction is executed every 1,000,000th of a second:
100,000*0.000001 = 0.1 seconds, or 100 milliseconds.

These delay macros are functions, and they are defined externally meaning the definitions don't reside within our own program file (the learn_c.c file that we write our program in).  Rather, these macros are defined externally in the pic.h header file, which is indirectly included in our program by the preprocessor directive #include <xc.h>.

MAKING OUR OWN FUNCTION

In our previous program we used bitwise operators to manipulate the led mask bit of our GPIO shadow register, then copied the shadow register to the GPIO port, which turned our LED on and off.  We can create functions to do this.  The ultimate goal of these instructions is to get the LED to turn on, and then to turn it back off, so let's create a function for each task.

We will name the function that turns the LED on "doLED_ON", and the function that turns the LED off "doLED_OFF".  We don't need to pass any parameters to the functions, nor will they return any results.  Let's do the LED_ON function first.

Let's declare our function, which is return_type function_name(parameter list). Our return type is void, our function name is "doLED_ON" and our parameter list is (void), thus we have:

void doLED_ON(void)

Now we need a function body, which is a list of instructions enclosed in braces { }.  To turn the LED connected to GP0 on, we ORed GPIOimg with the LEDmask and then copied GPIOimg to the GPIO register:

{
GPIOimg |= LEDmask;
GPIO = GPIOimg;
}

Putting this all together completes our function declaration.

void doLED_ON(void)
{
GPIOimg |= LEDmask;
GPIO = GPIOimg;
}

Doing the same thing for the LED_OFF function results in:

void doLED_OFF(void)
{
GPIOimg ^= LEDmask;
GPIO = GPIOimg;
}

We will want to dedicate a section in our program code to these function declarations.  This is shown in Figure 3.  I call these declarations "Shadowed I/O Routines" since the functions perform manipulations in the Input/Output bits via a shadow register.

Figure 3.
CALLING A FUNCTION

Now when we want to turn the LED on or off, we can simply call these functions to do that task.  If we had a long program that at various points turned on or off the LED, it is much simpler to have these functions and then simply call them throughout the program than to rewrite the instructions within the function over and over again.  When the program calls the function, the program flow jumps to the function declaration where the list of instructions there are executed.  At the end of the function, denoted by the closing brace "}" the program flow returns to where the function was called and continues on with normal program flow.  Figure 4 shows where the functions are called in our main program.

Figure 4.
Within our for function, the doLED_ON( ) and doLED_OFF( ) functions are called.  Calling a function is a statement and must be followed with a semicolon.  Additionally, since we are passing no arguments to the function when we call it, we must use empty parenthesis.  Let's look now at our full program, which is shown in figure 5.  You can update your program and flash it to your PIC. Program operation is exactly the same as our previous program, since our functions perform the same tasks that were replaced.  Again, all of our program hardware is unchanged so there is no need to change the breadboard.

Figure 5.
Global variables are defined and set, and our I/O map defines the initialization state of the TRISIO and GPIO registers, as well as defining our LEDmask bit location.
XTAL_FREQ is defined for our delay routines.
Our functions are then declared, and the instructions they perform are stated within the function bodies.
Our main program then starts by setting data direction in the TRISIO register (are the I/O bits input or output), clearing the GPIOimg variable (making all 8 bits zero), and clearing the GPIO register.
The endlessly repeating while(1) loop then begins, which contains a for function that defines a local variable "flashes" and makes flashes equal to the variable "count". For as long as flashes is greater than 0, this loop repeats and after each repetition, "flashes" is decremented (variable minus 1).
Within the body of for, the doLED_ON( ) function is called, which takes program flow to the function declaration. The instructions there are then executed, which ORs GPIOimg with the LEDmask and makes GPIOimg equal to the result. That result is then copied to the GPIO register. This function is now complete, and program flow returns to the for loop where we left off.
The program is now delayed by 100 milliseconds.
Now the function doLED_OFF( ) is called, which again breaks program flow and executes the instructions in the doLED_OFF function before returning to another 100 millisecond delay.
At this point "flashes" is decremented.  If flashes is still greater than 0, this loop is repeated until flashes is not greater than 0.
When that happens, the program delays for 500 milliseconds, then adds 1 to the variable "count".  Program flow now returns to the beginning of while(1).

This concludes the example of creating and using your own functions within your program.  What if however, you find that you repeatedly use the same functions in many of your programs?  Is there an easier way to include the function declarations in multiple programs without having to write them out in each one?  Yes there is!  We can create an external file for the functions and then just #include that file in each program.

CREATING AND USING EXTERNAL FUNCTION FILES

Let's create a new file in MPLAB X by clicking the new file button (Figure 6.) or by clicking on File> New File.
Figure 6.
Under "Project" make sure that our learn_c project is selected.
Under "categories" select "C" and then within the "file types" window select "C Header File".  This file type will include #define macros to prevent multiple inclusion, and contain external c statements so that it can also be included by C++ files (a feature we will not use on this blog).
Select the "next" button.
Name your file, I will name mine "functest", and be sure that the extension drop-down menu has "h" selected.
Both "project" and "location" should show "learn_c".
Note the "Created File" location or choose your desired location for this file
Now click on "finish".

You should now see a blank header file (Figure 7.)  You will see the macros and external c statements discussed just above for C++.

Figure 7.
After the last #endif we can start writing our functions.  This would be on line 23 in Figure 7.

We can copy our do_LED_ON and doLED_OFF functions to our functest.h file.  Now, something very important to remember: the functions include instructions that reference both the LEDmask and the GPIOimg declarations, but as far as functest.h is concerned, it doesn't know what these are.  Therefore, we will need to also include them.  The trade here is that since we have them defined in the external file, we don't need to define them again in our main c source file.  You'll see this in just a moment.

Figure 8.
Figure 8 shows the completed functest.h file starting on line 23. Reference Figure 7 for lines 1 through 22, as they are unchanged in the completed file.  You can see I have defined the LEDmask the same as in the original program, and I have defined the GPIOimg variable.  Following this is an exact copy of the function declarations from Figure 3 above.  With the functest.h window open, select File> Save (if save is greyed out, the file is already saved).  Now let's return to our learn_c.c file where our program is.
Remove the "Shadowed I/O Routines" section, since these are now in the functest.h header file.  We can also remove #define LEDmask 0b00000001 and unsigned char GPIOimg; since these definitions are also included in the header file.  Super Tip: to remove lines of code for testing purposes, or code you intend to put back, just "comment out" the code rather than erasing it. Then all you have to do is remove the comment markers and your code is as good as new.  We must now include the new header file in our code. Under the #define <xc.h> let's add #define "functest.h".  You might wonder why xc.h is in carrots and functest.h is in quotation marks.  The answer is simple, <carrots> are for system files, and "quotes" are for user files.

Figure 9 shows the main c source file with the functest.h file included, and the irrelevant code commented out.  In the Projects Window of MPLAB X, right click on the "Header Files" under the "learn_c" project and click "Add Existing Item". Find and select functest.h so that the file is included in your project hierarchy.  Clicking the "Build Project" icon in MPLAB X should successfully compile your project using the external functions, resulting in a "build successful" message.
Figure 9.
Now you know how to use your own functions in a program, and how to include them externally if you want to use them across many programs.  I hope this lesson was useful to you. Questions and comments are always welcome.  Thank you for reading!

3 comments:

  1. Hello!! I think your blog is great. I would like to say that in my humble opinion when the code of the header file should be from line 14 because if I understand it well the preprocessor directive are saying if this header file (functest.h) has not been defined (#ifndef FUNCTEST_H) define it (#define FUNCTEST_H). The next is saying something like if it is in c++ extend the c visibility (#ifdef __cplusplus then extern c{) then the curly brackets are closed in the same way and then the first(#ifndef FUNCTEST_H) if is closed.

    If we write after the last #endif this directives won't affect our code and it will be included as many times as we invoke #include "functest.h"

    I hope you will correct me if I'm wrong, and if I'm right I hope you will someway modified it so that everyone will lear it in the right way.

    There is also a tippo in the text you wrote #define "functest.h" instead of #include but I think people is getting out of the confusion when they see the picture.

    Once again I admire you for this great job you have done and I hope to learn more from you because you are a great teacher. Greetings.

    ReplyDelete
  2. Amazing article. Your blog helped me to improve myself in many ways thanks for sharing this kind of wonderful informative blogs in live. I have bookmarked more article from this website. Such a nice blog you are providing !
    C and C++ Training Institute in chennai | C and C++ Training Institute in anna nagar | C and C++ Training Institute in omr | C and C++ Training Institute in porur | C and C++ Training Institute in tambaram | C and C++ Training Institute in velachery

    ReplyDelete
  3. The Best Casino Sites in PA - MapyRO
    Best Casino Sites 고양 출장마사지 in Pennsylvania. 경상남도 출장샵 Best for slots. 888Casino is Pennsylvania's top choice for online gaming in 2021. 광명 출장안마 With 군산 출장안마 games from Microgaming, 공주 출장마사지

    ReplyDelete