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 Range of Values Memory
Usage
Used for
int1, short signed, unsigned unsigned: 0:1 1 bit Logical
values, true
and falase
int8, int signed, unsigned unsigned: 0 :15 8 bit Small
integer
values
signed: -8 : 7
int16, long, signed, unsigned unsigned: 0 : 255 16 bit Larger
integer
values
signed: -125 :
124
int32, signed, unsigned unsigned: 0 :
4294967296
32 bit Very large
values,
chances are
you will not
need this.
signed: -
2147483648 :
2147483647
float Automatically
signed
32 bit For dealing
with decimal
values, real
numbers
char 8 bit For dealing
with text
Void Nothing 0 bit Indicates no
specific data
type


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 specifier2 variable_name2,
type_specifierN variable_nameN;

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 Layout True when False when Used to
Equivalence a == b a is the same
value as b
a and b are
different values
Check for whether a
certain pa
And a && b a and b are both
true (not 0)
Either a or b is
false

Or a || b Either a or b is
true
Both a and b
are false

Test a When a is not 0 When a is 0.
Greater/Less
Than
a >= b, a <= b,
a < b, a > b
a is greater than
or equal to b,
etc
a is less than or
not equal to b
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