You are on page 1of 10

Coding The PIC Microcontroller With PIC C 1.

0 Coding in C:
C is the language we use to code PIC microcontrollers. Some people also use Assembly, we call them sadists. 1.1 Creating Variables: A variable is a name given to a section of memory. Memory is cataloged by things called addresses. These addresses are the locations for variables in memory. With PIC C, you will tend to deal with two types of variables. In one case, you will create a variable and have the chip itself handle what address it goes to. This is the case when you do not desire to create a variable for a certain part of memory, you just want to store a value. The other case is when you want to refer to a specific part of the memory, this will be dealt with in the PIC specific section. For now, lets look at just creating variables and not caring where they are stored. In order to create a variable, one must first know what kind of data that variable is going to store. Since the variable is just serving to refer to a certain section of memory, one must specify two things. The first is the amount of memory that variable is going to take up, the second is the value that the variable will have. Usually, the size (amount of memory) of the variable is going to be proportional to the range of values which it has to deal with. If one wants to create a variable that will receive inputs ranging from 0 to 50000, they will not choose a variable which can only cover a range of 0-255. Thus, it is important to always know before hand what kind of data will be stored and handled. To create a variable, one uses the following syntax:
variable_type type_specifier variable_name;

Variable_type refers to the kind of data one is trying to store, and by way of that it also specifies how much memory it will take up. Type_specifier is used to specify certain properties about that variable. Variable_name is the name which you will use to refer to that section of memory. The chart below shows the variable types, special settings for those types, how much memory those types take up, and what they types are generally used for. Variable_type Type-specifier int1, short int8, int signed, unsigned signed, unsigned Range of Values unsigned: 0:1 unsigned: 0 :15 signed: -8 : 7 Memory Used for Usage 1 bit Logical values, true and falase 8 bit Small integer values

int16, long,

signed, unsigned

unsigned: 0 : 255 signed: 124 -125 :

16 bit

Larger integer values Very large values, chances are you will not need this. For dealing with decimal values, real numbers For dealing with text Indicates no specific data type

int32,

signed, unsigned

unsigned: 0 4294967296 signed: -2147483648 2147483647

: 32 bit

: 32 bit

float

Automatically signed

char Void Nothing

8 bit 0 bit

So if I wanted to store a value that I knew would range from 0-200, I would enter:
int16 hume;

You may notice that I did not enter a type-specifier, that is because all variables besides float are assumed unsigned. If one wanted to deal with values ranging from -100 to 100, then you would declare the variable as:
signed int16 hume;

Another neat thing is that if one wants to declare several variables that have the same variable_type, one can easily do this using the following format:
variable_type type_specifier1 variable_name1, type_specifierN variable_nameN; type specifier2 variable_name2,

So for example, if you were creating several variables with the same variable_type, say two of them in the range from -100 to 100 and 2 in the range from 0 to 255, then you would enter:
int8 hume, turing, signed godel, signed sartre;

This is good and all, but what if we had one hundred variables we needed to store from 0 to 100? Would we fill up several lines with just declaring

variables? The answer is NO, instead we use what is called an array. An array allows you to use one variable_name, but have it store multiple values. What one does is basically create an index of variables, where each variable is given a unique number, but they are all under the same heading of the variable_name. The format for this is:
variable_type type_specifier variable_name[index_size];

This can be used in the following way. Say you want to store 100 different variables that are integers that range from -20 to 100, then you would enter the following code:
signed int8 hume[100];

Something to keep in mind is that if you want to declare an array, all the elements in that array will have the same variable_type and type_specifier. If you want to create a variable, and assign it a value also, this can be done in the following fashion;
variable_type type_specifier variable_name = value;

Where value is what you want the variable to be equal to. Variables are also dependent on where they are declared. If a variable is declared inside a function, it considered to be local to that function. This means that it only exists in that function, and when the function is exited, the variable too ceases to exist. It also means that the variable is only created when that function is called. The opposite of this is the global variable, which exists outside of all functions and can be used in any part of the code. It is created when the program is run. This will be covered in more depth in section 1.5. 1.2 Creating Functions: Functions are how one manipulates variables. It is easy to create a collection of variables, some of them large and very impressive. However, they are no good if not used properly, or at all for that matter. If one wants to create a function, it will generally take the follow form:
variable_type type_specifier Function_name (variable_type type_specifier variable_name1, variable_typeN type_specifierN variable_nameN) { Operations return something; }

The general structure of a function is such that it takes in data in the form of variables between the ( ) parentheses, does something with that inputted data in operations that are written in between these { } brackets, and then outputs a value with the return statement. Here is an

example function which will take in several values and add them. This function will be designed to only handle signed integers.
signed int32 godel (int16 signed russell, int16 signed whitehead) { godel = russell + whitehead; return godel; }

Functions can contain any number of operations. They can also contain any number of inputted variables. Something to keep in mind is that a function is exited once the return statement is run. If this function were to be called (executed) elsewhere in this program, it would be done this way:
Some_variable = godel(another_variable, yet_another_variable);

When creating a program, you must create a main function. A function with the name main is what the computer (microcontroller) will run when it is first turned on. This means that a main function is primarily used to organize all the other functions and variables so that they work together. A main function is declared just like any other function, just that its name is main:
Void main() { operations; }

You may notice that I did not include a return statement in the above function. This is because any function created with the variable_type void is not supposed to return a value. This is true for all functions, not just main. 1.3 Controlling Program Flow: The flow of a program is basically the order in which it executes the operations or functions that have been entered. When a program is written that has no flow control elements, it will simple run down each operation in sequence. For example:
Void main() { int8 hume, feynman; 1 signed int16 escher; 2 escher = hume + feynman; 3 }

This program when executed will perform the following commands in series, that is it will go from 1 to 3. However, sometimes it is not beneficial for the program to run through a bunch of operations in sequence. This means that some how one will have to make it so that the order in which the operations are written is not necessarily the order in which they are executed. One simple way to change this is by making the execution of an operation dependent on something. This involves using what is called an if statement. An if statement can be written as follows:
if (something_is_true) { operations; }

Something is true when it is not equal to 0. This is because c reserves 0 as being equal to false. The types of arguments that can be tested for whether they are true and false are: Argument type Equivalence And Or Test Greater/Less Than Layout a == b True when a is the same value as b a && b a and b are both true (not 0) a || b Either a or b is true a When a is not 0 a >= b, a <= b, a is greater than a < b, a > b or equal to b, etc False when a and b are different values Either a or b is false Both a and b are false When a is 0. a is less than or not equal to b Used to Check for whether a certain pa

Used mainly in counters, to tell when a function has been executed a desired number of times.

The next way to control what operations are done and what ones are not is with else statements. When an else statement proceeds and if statement, then the else statement will execute if the if statement is shown to be untrue. For instance:
if (something_is_true) { operations; } else { operations; }

If one is going to do a lot of if statements, and these if statements will be checking for equivalence for a variable, then it may be best to use a switch statement. This allows one to select a variable that will act as a switch. One then has a series of case values, each case being a possible value of the variable being used as the switch.
switch (variable) case possible_value: operations; break; case possible_value2: operations; break; case possible_valueN: operations; break; else break;

If you want to repeat a certain operation over and over again, one would use a loop. There are three types of loops, while, do while, and for. An example of each is given below. Each of the loops count up to a certain value and each turn output that value.
while (variable < constant) { variable++; printf(variable\n\r); } do { variable++; printf(variable\n\r); } while (variable < constant) for (variable = 0; variable < constant; variable++) { printf(variable\n\r); }

2.0 PIC Specific Functions and Variables:


2.1 Setting up a PIC: This involves declaring the settings that are going to be used on the PIC. There are many settings that are used depending on the task on wants to do. For our purposes we will always use the header written below;
#include <16f877.h>

#fuses HS, NOBROWNOUT, NOWDT #use delay (clock = 20000000)

2.2

Data I/O With a PIC: Data I/O can be done several different ways with the PIC. It really depends on what kind of data one wants to give out. In general, if one is outputting data that is just specifying things such as whether to flip a switch on or off, then it may be best to select a single pin and just modify its state. The other type of outputted data would be a variable such as an integer, in that case It works better to output it using the PICs built in serial capabilities. These will be explored in the next section. To modify specific pins on the PIC, there are several methods. However, the same thing that is needed for all of them is to declare the state of the pins being used on the PIC. This is done with the set_tris function:
set_tris_port(0bsome_byte)

What this means is that one can select the port (A,B,C,D) on the PIC which they wish to setup. The 0b stands for byte, and tells the compiler that you will be using a byte to specify the ports. This can also be done with hex, and in that case one would enter 0x, but the byte format is easier to understand at first glance. The some_byte is a series of 8 0s and 1s which specifies whether a pin on a certain port is input or output. If a pin is input, then one enters a 1, if the pin is an output, then the pin enters a 0. The example below shows one how to setup a port, port A specifically, so that pins 0-4 on port A are inputs, and 5-7 are outputs.
setup_adc(ADC_OFF); set_tris_a(0b00011111);

You may be wondering why I included setup_adc(ADC_OFF); in this code. The reason was that port A happens to be the same port where ADC functions are performed, and thus one must turn the ADC capabilities of that port off so that it can act correctly. Anyway, once the pins on a port have been declared the next step is to read them or change their states. To read a pin, use:
input(PIN_port&pin);

What that does is output the state of the pin on the port. So, if we wanted to read from our input pin 1 on port A, we would add the code:
Input(PIN_A1);

The other task is to set an output pin to a value, this is done a similar way. The only difference is that you have to specify in the function what values you want to output. Since the pins only output either a 0 or 1, you have two functions:
output_high(PIN_port&pin);

for high, or:


output_low(PIN_port&pin);

This is a simple way to do input and output, however it can get even simpler. This is done by declaring a variable that is to equal a certain pin or port. This is done with the #bit or #byte directive, respectively. In this case, you do not use the PIN_port&pin format to specify the pin or port, instead you use a numerical expression. #bit variable = port.pin #byte variable = port In these cases, port A is 5, B is 6, C is 7, and D is 8. For PICs which more I/O pins, the trend should continue. The pin decimals run from 0 to 7. So if I wanted to create a variable for each pin on port A, I would: #bit pina0 = 5.0 #bit pina1 = 5.1 #bit pina2 = 5.2 #bit pina3 = 5.3 #bit pina4 = 5.4 #bit pina5 = 5.5 Something to note here, the astute reader will not that I did not create variables for pins 6 and 7. This is because port a only has 6 pins, however the compilier treats it like it has 8 (as in the set_tris example), this is just one example of the PIC C craziness which will be encountered as you learn to work with PICs. Now that we can declare variables for specific PINs, we can check the value of an input pin by simply calling the variable, an we can change the value of an output pin by setting it to a value. While this is all good, it is sometimes better to control a whole port. In this case, we set a variable equal to the port. This is where the #byte command comes in. #byte porta = 5 This means that if we set porta equal to a value, it would adjust its pin values (assuming all are output, set_tris_a(0b00000000)) equal to the binary representation of that byte. On the other hand, one can also read the value of all the pins on port a, and interpret it as a byte (assuming all are input, set_tris_a(0b11111111)). 2.3 Serial Communication:

Serial communication is done on a PIC by specifying several things, the output pin, the input pin, the baud (connection speed), and several possible specific serial settings. To setup up a serial connection is easy. Note, only one serial connection can be setup at a time. To setup up a serial connection is easy. Note, only one serial connection can be setup at a time.
#use rs232(baud = some_value, rcv = PIN_port&pin, xmit = PIN_port&pin, other)

One also needs to make sure that the pins specified for rcv (input) and xmit (output) in the serial communication operation are also specified the same way in the set_tris operation. To do output, that is send a signal through the xmit pin, one would:
printf(variable);

To do input, two things must be done. The first is to wait for the input to come in. this is done with the kbhit(); function, the second is to grab that input. This is done with the getc(); function. To do this right, one must create a loop that runs until kbhit() is true. The next step is to getc():
While(!kbhit()); variable = getc();

This code waits for a character to be inputted, then sets variable equal to whatever was inputted. 2.4 Analog to Digital Converter Setup: An Analog to Digital Converter (ADC) is a circuit that takes a voltage in, and expresses that voltage as a byte. For robotics, this is especially useful for sensory systems which rely on continuous voltage changes to indicated changes in the real world. For the PIC, port A is the designated ADC port. This means that to read ADC data, port A must be used. One first needs to declare which pins they are going to use for the ADC inputs, this is done by using the setup_adc_ports function.
setup_adc_ports(ANALOG_R&pin);

An example of this is:


setup_adc_ports(ANALOG_RA0_RA1);

This would turn ports A0 and A1 into ADC ports. After the ports have been declared, one then sets the clock to be used by the ADC. This is done with:
setup_adc(adc_clock_internal);

After that, one then sets up the ADC channel. Like the serial port, only one channel can be read at a time. So if you want to change channels, you will have to redo set_adc_channel. It is also recommended that you delay the reading of the ADC port for a short time just so that it can give you a more accurate reading. So, if we want to setup a channel, and then wait a couple ms before reading, we would enter:
Set_adc_channel(pin); Delay_ms(short_time); Variable = read_adc();

The two functions introduced above, delay_ms and read_adc, do exactly what they are named. Delay_ms waits short_time number of ms until resuming the program. Read_adc reads the ADC pin as declared in the set_adc_channel.

You might also like