Coding The PIC Microcontroller With PIC C 1.0Coding in C:

fiercebunElectronics - Devices

Nov 2, 2013 (4 years and 8 days ago)

67 views

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 t
he 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 mus
t 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 i
t 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:


v
ariable_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_s
pecifier
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
tak
e 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

Sm
all
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 kne
w 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 declar
e 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:


v
ariable_type type_specifier1
variable_name1
, type specifier
2
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, turin
g, 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 arr
ay
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 t
his is:


v
ariable_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
v
ariable_type

and

type_specifier
.

If you want to create a variable, and assign it a value also, this can be done
in the following fa
shion;


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, d
oes 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 desi
gned 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);


Wh
en 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 vari
ables

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 functi
on 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 bee
n 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 st
atement can be written as follows:


if (
something_is_true
)


{


operations;


}




Something is true whe
n 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 fals
e are
:


Argument type

Layout

True when…

False when…

Used to…

b煵ivalence

a‽ ⁢

a is the same
value as⁢

a an搠 戠 are
摩fferent values

Chec欠 f潲 whether a
certain 灡

And

a…C b

a an搠戠are 扯bh
true (n潴 〩

bither a 潲 戠 is
false




a⁼| b

bither a 潲 b

is
true

_潴h a an搠 戠
are false


Test

a

then a⁩s n潴 0

then a⁩s 〮


dreater/iess
Than

a 㸽 戬ba 㰽 戬b
a‼⁢Ⱐ ‾⁢

a is greater than
潲 e煵al t漠 戬b
etc

a is less than 潲
n潴 e煵al t漠b

rse搠 mainly in
c潵ntersⰠ t漠 tell when a
functi潮 has 扥en
execute搠
a 摥sire搠
num扥r 潦 times.



The next way t漠 c潮tr潬 what 潰orati潮s are 摯de an搠 what 潮es
are n潴 is with else statements⸠ then an else statement 灲潣ee摳 an搠 if
statementⰠ then the else statement will execute if the if statement is sh潷n
t漠扥 untrue⸠
c潲 instanceW



i
f (
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_v
alue2:



operations
;



break;


case
possible_valueN
:



operations
;



break;


else



break;




If you want to repeat a certain opera
tion 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.



w
hile (
variable

< constant
)


{


variabl
e++;


printf(
variable
\
n
\
r
);


}


do


{


variable
++;


printf(
variable
\
n
\
r
);


} while (
variable

<
constant
)




for (
variab
le

= 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 t
he 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 out
putted data would be a variable such as an integer, in that case It works
better to output it using the PIC’s 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
(
0b
some_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
so
me_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 specif
ically, 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
functi
ons 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
&
pi
n
)
;




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 diff
erence 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:


o
u
t
put_high(PIN_
port
&
pin
);


for high, or
:


o
u
t
put_
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 de
clare 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
, rc
v

= 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 communi
cation 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:


p
rintf(
variable
);


To do input, two things must be done. The first is to wait for the input to
come in. this i
s 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())
;





var
iable

=
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

tha
t 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.


s
etup_adc_ports(
ANALOG_R&
pin
);


An example of this is:


s
etup_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 m
s until resuming the
program. Read_adc reads the ADC pin as declared in the set_adc_channel.