In this tutorial you
will be introduced to the programming language C, and the PIC16F873, a popular and very widely used m-controller (read micro-controller). m-controllers find use in devices that needs some intelligence but
not a huge amount of processing power (eg, fancy graphical interfaces, massive
computing needs). You can find these devices in cars (engine control, anti-lock
brakes...), in appliances, etc... There are many ways to program these devices,
but you will be using C to program the PIC to perform some fairly simple tasks.
C is often used with m-controllers because of
its small size, high speed, and the access it allows to the real-world. This
week you will get a short introduction to C as well as a brief look at some of
the capabilities of the PIC.
A good resource for help
with the PIC programmed with the CCS C-compiler is given at http://www.ccsinfo.com/forum/.
The PIC microcontroller
comes in a wide range of variants. You can check them out in data books
that are in the lab, or at the MicroChip web site. A m-controller is distinguished from a m-processor in that it has many capabilities
useful for real-world interfacing built into the chip. The PIC has a
RISC-based Harvard architecture with separate memory for data and
program. The one we will be using is the PIC16F873 (link to data sheet)
It has an on-board RAM (Random Access Memory), EPROM (Erasable
Programmable Read Only Memory), an oscillator, a couple of timers, and several
Input/Output (I/O) pins, serial ports and 8 channel A/D convertor (if you don’t
know what all of those things are don’t worry; suffice it to say there can be
an impressive array of peripherals built into the chip). However, the m-controller is less computationally capable than
mostm-processors due to the fact that they are used
for simple control applications rather than spreadsheets and elaborate
calculations. As an example, the PIC16F873 has 4096 words of memory for
program, and only 192 bytes of RAM, and can only operate with clocks up to 20
MHz on 8 bits of data (compared to megabytes of RAM, Speeds of a GHZ or more
and 32 or even 64 bits of data for many desktop systems). It also has no
facilities for floating point numbers... A pinout of the PIC16F873 is shown
below.
There are several pins
that are used to power the device. Many of the other pins have multiple
uses depending on how the device is programmed.
This week, and the next,
you will be using a m-controller in the lab.
These laboratories will serve as a brief introduction to the processor and to
programming in C.
Getting started with C.
A simple C program
A very simple C program
is shown below.
/*simple.c -- sets a pin low, then high*/
#INCLUDE <16f873 .h=".h"> 16f873>
#USE DELAY (CLOCK=4000000)
void main() {
output_low(pin_C1);
output_high(pin_C1);
}
This program has many
common features of C programs. The first line is a comment that is ignored by
the compiler. It is simply there to document what the program code
does. A comment is anything that occurs between a "/*" and the
subsequent "*/". The next line is a
"directive". All directives begin with a "#" and are
used to convey information to the compiler. The directive in this
program tells the compiler to include a header file ("16F873.h")
which is necessary when using the microcontroller’s input and output
capabilities. The next two directives tell it how to configure the
device, and how fast it goes. The next line tells the compiler that this
is the "main" program, and that everything between the opening brace,
{, and the matching closing brace, , constitutes the main program. The main
program itself is only two lines. The first line (not a
comment) is a call to the "output_low" function, which sets output
pin pin_C1 low, and a call to output_high, which sets it high. . Note that after every program
statement there is a semi-colon. This is very important.
Variables
Almost all programs will
use variables which are simply units of information stored in the computers
memory. The standard C language has a wide variety of variable types available,
however the dialect we will be using is more restricted. The version of C that
we will be using has a quite unstandard set of variable types that are suited
to its architecture.
Type
Specifier
|
Size
|
Range
|
unsigned
|
8 bit unsigned
|
0
to 255
|
unsigned int
|
||
int
|
||
char
|
||
int8
|
||
long
|
16 bit unsigned
|
0
to 65535
|
long int
|
||
int16
|
||
signed
|
8 bit signed
|
-128
to 127
|
signed int
|
||
signed int8
|
||
signed long
|
16 bit signed
|
-32768
to 32767 |
signed int8
|
||
int32
|
32 bit unsigned
|
4*109
|
signed int32
|
32 bit signed
|
±2*109
|
float
|
32 bit floating point
|
±0.5*2-128 to
1-(2-15)*2128
|
short
|
one bit
|
0
to 1
|
short int
|
||
int1
|
The program below shows
how variables are used.
#INCLUDE <16f873 .h=".h"> 16f873>
#USE DELAY (CLOCK=4000000)
void main() {
char i, j, k; /* declare characters */
i=2;
j=3;
k=i+j;
}
Again we have a fairly
simple program that shows many different features of C. Note the semicolon
after every program statement. We declare 3 char’s, "i",
"j" and "k". A char is simply an 8 bit
variable. You should use chars whenever possible because the PIC is
designed to work on data 8 bits at a time.
Numerical
manipulations
C has a variety of built
in operations for performing math. These are listed below along with an example
where a=0x03, and b=0x11:
|
Name
of Operand
|
Symbol
|
Example
|
Result
a=0x03 b=0x11
|
Binary
Operators (Two Operands)
|
Addition
|
+
|
a+b
|
0x14
|
Subtraction
|
-
|
b-a
|
0x0E
|
|
Multiplication
|
*
|
a*b
|
0x33
|
|
Division
|
/
|
b/a
|
0x05
|
|
Modulus
(remainder) |
%
|
b%a
|
0x02
|
|
Bitwise
and
|
&
|
b&a
|
0x01
|
|
Bitwise
or
|
|
|
b|a
|
0x13
|
|
Bitwise
xor
|
^
|
b^a
|
0x12
|
|
Shift
right
|
>>
|
b>>a
|
0x02
|
|
Shift
left
|
<<
|
0x88
Unary
Operators (One Operand)
increment
++
++a
0x04
decrement
--
--a
0x03
negate
-
-a
-0x03
logical complement
~
~a
0xFC
Logical Expressions
In addition ot
manipulating numbers, C is also capable of handling logical expressions. When
using these expressions TRUE is taken to be anything that is not zero, and
FALSE is always equal to zero. Again, a=0x03, and b=0x11:
Binary operators (two
operands)
Name
of Operand
|
Symbol
|
Example
|
Result
a=0x03 b=0x11 |
|
Binary
Operators
|
Greater
than
|
>
|
a>b
|
FALSE
|
Less
than
|
<
|
a
|
TRUE
Equal
==
a==b
FALSE
Greater
than or equal
>=
a>=b
FALSE
Less
than or equal
<=
a<=b
TRUE
Not
equal
!=
a!=b
TRUE
Logical
AND
&&
a&&b
TRUE
Logical
OR
||
a||b
TRUE
Unary
operators (one operand)
Logical
complement
!
!a
FALSE
Manipulating addresses (somewhat advanced topic, may be skipped)
There are two operators
used for manipulating addresses and you have already been briefly introduced to
one of them, the indirection operator, *. The other one is the address operator
&. If you have an address k, the value stored at that address is *k. If you
have a variable j, the address of the variable is given by &j. Therefore it
is obvious that *(&j)=j.
I/O (Input/Output) Ports
It is possible with with
a PIC to interact with the real world. This is done thourgh the use of
I/O ports. The PIC16F873 has 3 I/O ports, labeled "a",
"b" and "c". We will use Port A for analog input,
though it has other uses. Ports B and C will be used for digital
I/O. On the schematic the pins are labeled RB0 through RB7, but the
compiler refers to them as pin_B0 through pin_B7. Likewise for port
C. The pins can be used for either input or output.
Your circuit has the
pushbutton switch connected to RB0, and the LED's to pins RC0 through RC7.
Digital Output
There are several functions
that are used for output from the PIC. A full listing is in the PCB
manual. Four commonly used functions are:
·
Output_high(pin)
Sets the specified pin to a logic 1 (about 5 volts).
Sets the specified pin to a logic 1 (about 5 volts).
·
Output_low(pin)
Sets the specified pin to a logic 0 (about 0 volts)
Sets the specified pin to a logic 0 (about 0 volts)
·
Output_float(pin)
Sets the specified pin to a high-impedance (or tri-state) state. In this state it is as if the pin has no connections to the chip. Current can neither go in or out of the pin.
Sets the specified pin to a high-impedance (or tri-state) state. In this state it is as if the pin has no connections to the chip. Current can neither go in or out of the pin.
·
Output_bit(pin, value)
This function sets the specified pin to the specified value (which must be 0 or 1).
This function sets the specified pin to the specified value (which must be 0 or 1).
Digital Input
There is only one input
function you will need for the PIC.
·
Input(pin)
Reads the value on a specified pin. The value is returned in a short int. A proper use of the function would be something like:
while( !input(pin_B0)) { ... }
which would repeat the commands in the braces as long as RB0 was low.
Reads the value on a specified pin. The value is returned in a short int. A proper use of the function would be something like:
while( !input(pin_B0)) { ... }
which would repeat the commands in the braces as long as RB0 was low.
"High level" I/O
If the PIC is connected
to the PIC C development software via the debugger it is possible to do some
higher level input and output. These interactions take place via
the debugger's "Monitor" window.
You specify that IO is
to take place through the debugger by properly defining serial connections
(usually in your codes header file):
#use
rs232(DEBUGGER)
You can then print to
the monitor window by using "putc()" which sends a character to the
monitor window, "puts()" which sends a string, or
"printf()" which sends a formatted string. The
"printf()" command is most useful, but also the most complicated (and
takes the most memory).
The syntax of printf is the following:
printf(format-string, [arg_1] , ... , [arg_N] )
This is best illustrated
by some examples.
Example 1: Printing a
message. The following statement
prints a text string to the screen.
printf("Hello, world!\n");
In this example, the format string is simply printed to the
screen.
The character \n at the end of the string
signifies end-of-line. When an end-of-line character is printed, the LCD screen will be
cleared when a subsequent character is printed. Thus, most printfstatements are terminated by a \n.
Example 2: Printing a
number. The following statement
prints the value of the integer variable x with a brief message.
printf("Value is %d\n", x);
The special form %d is used to format the
printing of an integer in decimal format.
Example 3: Printing a
character. The following statement
prints the ascii equivalent of the integer variable x with a brief message.
printf("Value is %d, ascii = %c\n", x, x);
Example 4: Printing a
number in binary. The following statement
prints the value of the integer variable x as a binary number.
printf("Value is %b\n", x);
The special form %b is used to format the printing of an integer in
binary format. Only the low byte of the number is printed.
Example 5: Printing a
floating point number. The following statement
prints the value of the floating point variable n as a floating point number.
printf("Value is %f\n", n);
The special form %f is used to format the printing of floating point
number.
Example 6: Printing two
numbers in hexadecimal format.
printf("A=%x
B=%x\n", a, b);
The form %x formats an integer to print in hexadecimal.
%d
Type: int Description: decimal number
%x
Type: int Description: hexadecimal number
%b
Type: int Description: low byte as binary number
%c
Type: int Description: low byte as ASCII character
%f
Type: float Description: floating point number
%s
Type: char array Description: char array (string)
Format
Command
|
Data
Type
|
Description
|
%d
|
int
|
decimal
number
|
%x
|
int
|
hexadecimal
number
|
%b
|
int
|
low
byte as binary number
|
%c
|
int
|
low
byte as ASCII character
|
%f
|
float
|
floating
point number
|
%s
|
char
array
|
char
array (string)
|
Your circuit has the
pushbutton switch connected to RB0, and the LED's to pins RC0 through RC7.
Input
Unfortunately, you can
only receive input from the keyboard one character at a time using the getc()
command. Be aware:
·
getc() returns the ascii
equivalent of the character entered into the keyboard.
·
the keyboard I/O is
implemented in software on the PIC. That means, it won't receive input
from the keyboard unless it is explicitly looking for it. Therefore, your
program must stop in order to look for input from the keyboard. (Hardware
communications could receive a character in the background, without requiring
software support).
As an example, the
following code gets the ascii value in k, converts to a number, and prints the
number.
k=getc();
%Get ascii value of keyboard input.
k=k-'0'; %Subtract value of '0' to convert to number.
printf(" ... k=%d\n",k); %print the number.
k=k-'0'; %Subtract value of '0' to convert to number.
printf(" ... k=%d\n",k); %print the number.
Control of Flow
What you have learned up
to this point has been useful but is of limited utility because it does not
allow for decision making capabilities by the computer. C has a variety of
mechanisms to control the flow of a program. These are listed below:
The if...then construct
if (logical
expression) {
...statements...
}
...statements...
}
If the logical
expression is true then evaluate the statements between the braces. The
following code sets RC1 if a is even.
if ((a%2) == 0) {
Output_high(pin_C1);
}
Output_high(pin_C1);
}
The if...then...else construct
if (logical
expression) {
...if statements...
}
else {
...else statements...
}
...if statements...
}
else {
...else statements...
}
If the logical
expression is true then evaluate the "if" statements between the
braces, otherwise execute the "else" statements. The following code
decides if a number if is even or odd.
if ((a%2) == 0) {
Output_high(pin_C1);
}
else {
Output_low(pin_C1);
}
Output_high(pin_C1);
}
else {
Output_low(pin_C1);
}
while (logical expression) {
...statements...
}
While the logical expression
is true, the statments (of which there is an arbitrary number) between the
braces is executed. The following code cycles through the even numbers from 0
to 9 (the variables must have been declared elsewhere in the program).
a=0;
while (a<10 o:p="o:p">10>
...statements...
a=a+2;
}
The for loop
for (initial
condition; logical expression; change loop counter variable) {
...statements...
}
...statements...
}
Set the initial
condition, then while the logical expression is true execute the statement
between the braces while changing the loop counter as specified once at the end
of each loop. This code segment is functionally equivalent to the one for the
"while" loop.
for (a=0; a<10 a="a+2)" br="br">
...statements...
}10>
}10>
The case...switch construct
Case..switch is used in
place of a series of "if...else" clauses when a variable can take on
several values. The "default" clause at the bottom takes care
of any cases not covered explicitly.
switch (variable) {
case val1: ...statements 1...
break;
case val1: ...statements 1...
break;
case
val2: ...statements 2...
break;
break;
case
val3: ...statements 3...
break;
break;
default:
...statements default...
break;
break;
}
Functions
Often a series of
instruction must be repeated over and over again. Instead of repeating the same
operations repetitively it is useful to use a function that performs the
repetitive operations. For instance to set a value on RC1 and then read from
RB0 and set RC0 to that value, and returning the value of RB0 you might use a
function called "RB0toRC0". (Note: this program isn't meant to
be particularly useful, but to introduce the syntax for function declaration,
and use).
/*simpleFunc.c -- to demonstrate function calls*/
#INCLUDE <16f873 .h=".h"> 16f873>
#USE DELAY (CLOCK=4000000)
short int RB0toRC0(RC1val)
short int RC1val;
{
Output_bit(pin_C1,
RC1val); /*Set RC1 to the specified
value*/
if (input(pin_B0))
{ /*Read RB0*/
Output_high(pin_C0); /*If RB0 is high, RC0 set high*/
}
else {
Output_low(pin_C0); /*else
set RC0 low*/
}
return(input(pin_B0));
/*Return RB0*/
}
void main() {
short int b;
b=RB0toRC0(1);
b=RB0toRC0(0);
}
This program introduces
some new constructs. The most obvious is the function "RB0toRC0"
which is at the top. The first line of the function declares that the function
returns a short int, and has one argument. The type of the argument is given
before the function's opening brace. The body of the function (between the
braces) outputs a value to RC1, and reads from RB0 and echos to RC1. The
last line tells that compiler to return the value of RB0 to the calling
function.
The function is called
in the main program with different arguments. The first call would set
RC1 high, and return the current value of RB0 to the variable
"b". The next line would set RC1 low, and return the value of
RB0 to the variable "b".
Wrapping up
You should now know
enough to do some fairly simple things with the microcontroller. This has been
a very brief introduction to C and did not even begin to touch the richness
available with the language. If you would like to know more look in the manuals
in the lab, or some of the books in the library.