You are on page 1of 66

A PIC microcontroller is essentially a tiny computer inside a single chip.

It is an integrated circuit (IC) that


includes a CPU, a small amount of RAM (less than 1kb), a small amount of EEPROM/FLASH-based
program memory (a few thousand kb) for program storage, a few input/output pins, and some hardware
peripherals like timers, ADC, UARTs, etc.
The popular PIC16F84a (shown below), for example, has a RISC CPU, a 1024-word program memory, a
mere 68 bytes of data RAM, and a maximum clock speed of 20 Mhz.

FIGURE 1. 18-pin PIC16F84A


Since the PIC MCU (short for MiCrocontroller Unit) is technically a complete computer on its own, it would
help to think of the program memory as equivalent to the hard disk of a desktop or laptop computer and the
data RAM as equivalent to a PCs main memory. As comparison between a PIC16F84a MCU and a typical
desktop PC, refer to the table below:

Table 1. A comparison

Based on the table above, you may conclude correctly that the PIC16F84A MCU pales in comparison with the
desktop PC. The MCUs cant perform word processing nor can you play your favorite PC games with it.
Nevertheless, MCUs has sufficient computing capability to control, say, a DC motor, display text to an LCD,
turn on a relay, scan a keypad, control a small robot, display numerical values to a 7-segment display (like in
Figure 2 below), send a serial data to PC, etc. Its tiny form factor makes it easy to embedded into many
applications. And its cost /computing power is quite astonishing for its size. A several-million dollar supercomputer of the late 1960s weighs several tones, fit into a very large room (a hall, actually
), and is 1000
times slower than this MCU.

FIGURE 2. The PIC16F84a on an application board


MCUs are so successful that for every desktop/laptop computer in the world today, there are at least 10 MCUs running
on some embedded platforms. You can find them on your air conditioning system, microwave oven, cars, toys,
electronic gadgets, etc. Even the motherboard of your PC has at least 1 MCU on it! Think keyboard controllers.
Many argue that the computing revolution that is happening for the past 30 years is mainly due to microcontrollers and
not just the typical PC. When this tutorial is over, you might agree with the last statement.
Based on my *limited experience
(and those of others), what you can do with an MCU is virtually limited by your
own imagination as well as your technical knowledge. (Nope, it cant water the plant or bake you some cookies. Or can
it?)
Imagination we have in abundance. Technical knowledge, well, that is something that this on-line tutorial will
provide.
Ok, naff said. Lets start programming, shall we?

Let's continue...
Figure 4 below shows a typical schematic for PIC16F84a circuit. The PIC MCU is a +5V-powered device (5V
supply connected to VDD pin). The device datasheet indicates a VDD(MIN) = +4V and VDD(MAX) = +5.5 V, so we
are on the safe side if we use 5V. Its also easier to generate +5V using the widely available LM7805 voltage
regulators.

Figure 4. A simple PIC16F84a circuit.


In the Reset Circuit shown, R1 pulls up the /MCLR pin (Master CLeaR) to +5V and the MCU executes the stored program
as long as this pin is high (i.e. +5V). If SW1 pushbutton is pressed (and then released), /MCLR is pulled low momentarily
to 0V via R2 and the shorted SW1 and causes the MCU to reset the program execution.
The oscillator circuit uses a 4Mhz crystal oscillator with two 22-pf ceramic capacitors. Since a MCU is a sequential digital
machine (like all computers), it needs an astable (square wave) signal source to synchronize the several hardware
operations and program executions inside the MCU. Think of the function of the oscillator circuit as similar to a traffic
light that synchronize/regulates traffic flow on a main street.
The PIC16F84a MCU has 13 general purpose input/output (GPIO, or simply I/O) pins; RB0 to RB7 (collectively named as
PORTA) and RA0 to RA4 (PORTA). The user may connect any electronic component/devices to these GPIO pins to
implement any simple to complex applications.
You may think of the GPIO pins as the most important (and most abundant) part of the PIC that you can see. How else
can you make useful applications without these I/O pins?

If you want to build the PIC circuit on a breadboard, you may want to use the circuit shown below. The +5V
supply is provided by a 7805 voltage regulator, and shown in the schematic for completeness.

Figure 5. A simple PIC16F84a circuit: Breadboard version


Here is the minimalist PIC circuit as an alternative (appropriate for the peso-conscious individuals

Figure 6. Minimalist PIC16F84a circuit

).

In the circuit above, /MCLR pin is always pulled up to +5V. This may be a disadvantage if PIC MCu locks up
and stop executing the program due to power glitches or excessive electrical noise (for example, a large motor
nearby is generating large electrical fields and affect the MCU circuitry). Without SW1 in the previous circuit,
the user will have no means to reset the device other than to disconnect/reconnect the +5V power supply.
The oscillator circuit also uses an RC circuit (R2 and C1 above). This is just one of the five ways of clocking a
PIC (the first one is already shown in the Figure 4/5 using a crystal oscillator). It is also cheap (<P1.00) and is
an acceptable clock source for low cost, timing-insensitive applications. RC-based oscillator circuits are
generally limited to clock frequencies less than 1 Mhz and its main disadvantage is its inaccuracy.
As recommended in the datasheet, you may use R values between 5k and 100k and C values equal or greater
than 20 pf. As a start, try using 10kohm/20 pf RC values. You may get clock frequency ranging from 600 kHz
to 650 kHz, but dont freak out if you are 20% above or below that range. Again, its not accurate!
Now, let us improve the circuit in Figure 4 by connecting an LED to the RB0 pin. In our very first example
program, we will configure RB0 as an output pin, and then command the MCU to output a logic 1 at this pin. If
an output pin (like RB0) has logic 1, it means that a 5V signal appears at this pin with respect to circuit ground.
Simply put, our program will turn on the LED connected at RB0, since the 5V will forward bias the LED. This
is probably the simplest program that we can start with.

Figure 7. LED connected at RB0 pin

You may now build the circuit in Pr*oteu*s (Figure 8 ). Its good to start exploring the features of ISIS*7 (the
Pr*oteu*s schematic capture program). (Im not gonna make a Proteus tutorial anytime soon, so you have to
learn it yourself )

Figure 8. LED connected at RB0 pin


So how do we start programming a PIC16F84a in order for it to turn on an LED? What are the commands to
perform? What are the buttons to click? Well, hold your keyboard and your fingers first hehe lets discuss
a few things about the PIC itself.
Inside the PIC MCU are the so called special function internal registers (SFR). For a PIC16F84A, there are
about almost two dozens of these registers and all are 8-bit wide (NOTE1). These SFRs control the operation of
the MCU device. The user has to access and modify the value that these SFRs hold in order to control the
operation of the MCU. Table 2 below shows a summary of the special function registers. Each SFRs has its
own name.

TABLE 2. Summary of the PIC16F84A special function registers. Darkened spaces


indicates unimplemented registers and bit locations.

NOTE1: In Microchip literature, the internal registers are classified into two: (1) the special function registers
(SFR) and the (2) general function registers (GFR). The GFR (or GPR, general purpose registers) are the
main memory of the MCU. They are simply called the RAM.

The user doesnt have to know all of these SFRs to be able to use the MCU, but applying the 10% principle on
Getting Started means we have to understand maybe 2 or 3 of these special function registers at the start. And
that is what we are going to do.
The user can also think of the MCU as being controlled by the program stored in the MCU. The program in
turn, access and modify the SFRs content, as well as do some other things.

Furthermore, you can also view these special function registers as tiny switches that the program can turn on
and turn off. (Physically, a register is made up of flip-flops which can hold binary logic values in the form of
stored electric charges. But the physical make-up of the registers is not important for us MCU users. The
register/switch analogy should be sufficient for understanding).
Since the internal registers are normally 8 bits wide, then a register can be thought of as a group of 8 switches,
1 switch corresponds to a bit. These tiny switches can be turn on/turn off by writing a 1 or 0, respectively.
The RB0 pin (as well as the rest of the PORTB pins; remember that PORTB = RB<7:0>) is controlled by two
SFRs, namely, TRISB and PORTB (indicated in Table 3 below). TRISB is called the PORTB Data
Direction register. The binary value stored in the bit locations of TRISB will determine whether a PORTB pin
is an input or an output pin. If a TRISB bit is 0, then the corresponding RBx pin is an output pin; if 1, then it is
an input pin.
For example, since we want RB0 to be an output pin, then TRISB<0> (it means TRISB bit 0) must be 0. If we
want RB6 to be an input pin, then TRISB<6> must be 1.

Table 3. TRISB and PORTB SFRs

As another example, lets say that TRISB register holds a binary value of 11110000 (Table 4). This means that
RB<3:0> pins are output pins while RB<7:4> pins are input pins. (Figure 9)

Table 3. TRISB = 11110000. RB<3:0> are output and


RB<7:4> are input pins.

Figure 9. TRISB = 11110000 (F0 in hexadecimal notation)

Remember that at any one time, a GPIO pin is either an input or an output pin. An output pin can send an
output signal (either 5V or 0V), and an input pin can read an external signal (5V or 0V).
The PORTB register is the Data Latch register for PORTB/RB<7:0> (NOTE1). The value stored in PORTB register will be
latched out (i.e. sent) to the physical RBx pin(s) if the pin(s) is configured as an output pin. If the PORTB pin is an input
pin, the external logic signal appearing at the pin will be latched into the PORTB register. For example, if RB0 is
configured as an output pin (via TRISB), and a logic 1 is written/stored into PORTB<0>, then a +5V signal will appear at
the RB0 pin (NOTE2). If logic 0 is written into PORTB<0>, a 0V will appear instead.
Also, if for example RB7 is configured as an input pin, then an external +5V signal appearing at the pin (from an external
unknown circuit) will cause a 1 to be written into PORTB<7>. If the external signal is 0V, a 0 is written instead.

NOTE1:
Dont be confused with PORTB, the internal register, with PORTB, the collective name of the 8 external pins (that is,
RB<7:0>) you see dangling at the side of the chip. If you are confused, well, that is still ok because they are basically
synonymous (PORTB<0>, the least significant bit is the basically same as RB0 (pin6) in the discussion), according to the
programmerss perspective. But for the hardware purist (or the clueless
), they are actually different. The first PORTB
is a flip-flop (a register) inside the MCU, while the 2nd PORTB are the 8 visible pin connector.
The 8 pins have names, RB0..... RB7, which happens to be the name of the individual bits in the PORTB register.
Confused, still?

Well, never mind or better yet, read it again

NOTE2:
The +5V signal is coming from the MCU itself. It is safe to think that there is an imaginary +5V battery source inside RB0.
When PORTB<0> is set (i.e. 1 is written), an imaginary switch is closed and connects the positive terminal of the
imaginary battery to the external RB0 pin. The imaginary switch can then be controlled via software and you can do
many things with this capability, like turn on/off an LED, switch a relay, start/stop a motor, send a signal to an LCD, hack
a PC, or nuke a city somewhere in Africa, etc.
The same discussion above applies to RA<4:0> pins, but using PORTA and TRISA registers instead. PORTA only have 5
pins for the PIC16F84A chip, and are mapped to the lower 5 bits of the PORTA and TRISA SFRs.
In addition, when the PIC16F84a device is first powered up or when it is reset, all the GPIO pins are configured as input
pins. TRISA and TRISB are all 1s. You may verify this in Table 2, 2nd column from the right.
By now, you would have already guessed how to turn on an LED connected at RB0. To do this, TRISB<0> must be cleared
(i.e. written with 0) and PORTB<0> must be set (i.e. written with 1).

Lets make our very first Hello, LED program. This is what we need to do; make TRISB<0> = 0 and
PORTB<0> = 1, and voil! the precious LED lights up.
In Hi-Tech C, the line
Code:
TRISB = 0b00000000;

will configure RB0 as an output pin. Since the rest of the upper 7 bits are also 0, the rest of PORTB pins are
also configured as output pins. This shouldnt bother us much since nothing is connected to the RB<7:1> pins.
The prefix 0b will tell the compiler to interpret the next 8 0s as representing a binary number (the decimal 0).
Next, the line
Code:
PORTB = 0b00000001;

will command the PIC to output a +5V signal at RB0 pin, since PORTB<0> is 1. This will turn on the LED.

Now lets write the complete Hello, LED program.


Code:
#include <pic.h>
void main()
{
TRISB = 0b00000000;
PORTB = 0b00000001;

//PORTB are output


//LED on

while(1);

//infinite loop

Make a new project in MPLAB. You can name the project any name you want. Hello_LED is a good name.
Type the sample code above and dont forget to set the correct CONFIGURATION bits in the Configure
menu (XT, OFF, OFF, OFF). Compile/build the project.
In the project directory, there are several output files generated during the compile/build process. The most
important output files are the hello_led.coff and hello_led.hex.
The next step is to include the hello_led.hex (or hello_led.coff) into the simulated circuit in Proteus.
In the PIC circuit in ISIS (shown in Figure 8 above), double click the PIC16F84a device on the edit window.
This will open the Edit Component window. (Figure 10)

Figure 10. Edit Component window

In the Program File, click the Browse button. In the browse window, go to the MPLAB project folder (Figure

11)

Figure 11. The HEX and COFF output files. (hey i named my project turn_on_led,
instead of hello_led
)

Select either of the 2 files (hex or coff), then click Open. In the Edit Component window, choose the desired
Processor Clock Frequency (4Mhz) and click OK.
Then click the Play button located in the bottom left portion of the screen. The simulation will start and you can
see the LED light up (Figure 12).

Figure 12. Hello, LED program successful simulation


In proteus, you may choose not to include the external oscillator in the circuit since the model obtain its clock frequency
info. from the device property.

But in an actual hardware the oscillator is needed

Lets dissect the sample program.


The first line in the code is
Code:
#include <pic.h>

This line tells the compiler to include a file named pic.h (this is called a header file, located in the include
folder in the Hi-tech C installation directory). Since we are using a PIC16F84a device, the pic.h file will in turn
include another header file named pic1684.h. The 2nd header file is needed since the names of special function
registers are defined in this file, like TRISB and PORTB which we are using in this example program. You
may open pic1684.h and take a look at all the defined register names as well as names of individual bits of these
registers. See Figure 13 below for a screenshot of the pic1684.h file.

Figure 13. The pic1684.h header file. The TRISB and


PORTB registers are defined in this header file

If you leave out the #include <pic.h> line, the compiler will output an error message
Quote
undefined identifier: TRISB
undefined identifier: PORTB

since it doesnt know what TRISB and PORTB is.


The next two lines
Code:
TRISB = 0b00000000;
PORTB = 0b00000001;

//PORTB are output


//RB0 pin will output a logic high
//LED will turn on

will configure all PORTB pins as output and output a logic high at RB0 pin. A +5V will appear at RB0. Since
an LED is connected at RB0, it will light up.
The last line is an infinite loop.
Code:
while(1);

This empty while() loop will keep on iterating (but it executes nothing) since the condition inside the
parenthesis will always be true (i.e. 1 = TRUE, 0 = FALSE). It is included to keep the main program from
exiting. Without the infinite loop, the MCU will execute the program, then exit, then re-execute again the
program (since the address pointer will overflow back to the start of the program), execute, exit again and so on.

Lets modify the program above and use hexadecimal notation instead of binary notation.
Code:
#include <pic.h>
void main()
{
TRISB = 0x00;
PORTB = 0x01;
for(;;);

//PORTB are output


//LED on
//infinite loop
//similar to while(1);

In our previous program, we modified the whole TRISB register even though we are using RB0 only. RB<7:1>
were also configured as output. However, it is a good programming practice to modify only the relevant register
bits and leave the other bits unchanged. Therefore, if we want to configure RB0 as an output pin and leave the
remaining PORTB pins as unmodified, the line
Code:
TRISB = 0x00;

is not a good code.


For good programming practice, follow this simple bit-wise manipulation technique.
1. to clear a bit in a register, AND this bit with 0. The remaining bits must be AND with 1 in order to remain
unmodified
2. to set a bit in a register, OR this bit with 1. The remaining bits must be ORed with 0 in order to remain
unmodified.
As an example for (1) above, consider our previous sample program. We need to AND TRISB<0> with 0 to
clear this bit, and AND TRISB<7:1> with 1 in order for the remaining 7 PORTB pins to remain unchanged.
The code should be
Code:
TRISB = TRISB & 0b11111110;

The & is the symbol for bitwise AND operation. TRISB<0> is now 0 while TRISB<7:1> bits remain
unmodified.

As an example for (2) above, assume PORTB<0> must be set (i.e. written with 1) like in the Hello, LED sample
program. The code should be
Code:
PORTB = PORTB | 0b00000001;

The | is the symbol for bitwise OR operation. PORTB<0> is now 1 while PORTB<7:1> bits remain unchanged.
In shorthand C notation, the two line above are normally written as
Code:
TRISB &= 0b11111110;
PORTB |= 0b00000001;

or
Code:
TRISB &= ~0x01;
PORTB |= 0x01;

We will be using the 2nd example frequently.

We now have our 3rd Hello, LED sample program.


Code:
#include <pic.h>
void main()
{
TRISB &= ~0x01;
PORTB |= 0x01;
while(1);

//RB0 is output
//LED on
//infinite loop

Another example, if you want to use RA3 instead of RB0, here is the code.
Code:
#include <pic.h>
void main()
{
TRISA &= ~0x08;
PORTA |= 0x08;

//RA3 is output
//LED on

while(1);

//infinite loop

If there are 4 LEDs at RB<3:0>, here is the code:


Code:
#include <pic.h>
void main()
{
TRISB &= ~0x0F;
PORTB |= 0x0F;
while(1);

//RB0, RB1, RB2, and RB3 are output


//turn on the 4 LEDs
//infinite loop

If you are designing a line follower mobot that use 3 sensors connected to RB<2:0> and 2 h-bridges connected
to RA<3:0>, here is the (*incomplete) code
Code:
#include <pic.h>
void main()
{
//other initialization codes
TRISB |= 0x07;

//RB0, RB1, RB2 are input

TRISA &= ~0x0F;


PORTA &= ~0x0F;

//RA<3:0> are output


//turn off the two motors

while(1)
//infinite loop
{
//mobot codes goes here

}
}

If you dont like the previous codes above for modifying bits in registers, you can always use the name of the
individual bits of registers instead.
Code:
#include <pic.h>
void main( )
{
TRISB0 = 0;
RB0 = 1;

while(1);

//RB0 pin is configured as an output


//RB0 pin output an approximtely 5V DC signal
//and LED will turn on
//This is an empty infinite loop

//and will keep the program


//from exiting
}

TRISB0 is the name of TRISB<0> bit and RB0 is the name of PORTB<0> bit.

Here is our 5th version of the Hello, LED program


. This will demonstrate how to use macro-names in C.
The use of macro-names is a nice C language feature which can improve the program clarity.
Code:
#include <pic.h>
#define LED
#define ON

RB0
1

void main( )
{
TRISB0 = 0;
LED = ON;

while(1);

//RB0 pin is configured as an output


//RB0 pin output an approximtely 5V DC signal
//and LED will turn on
//This is an empty infinite loop
//and will keep the program
//from exiting

The line
Code:
#define LED

RB0

defines an identifier LED. The identifier LED is called a macro-name, or simply a macro. When the compiler
encounters the macro-name in the program, it will be replaced/substituted with RB0.
The next line
Code:
#define ON

defines the macro-name ON with its corresponding value 1.


Therefore, the line in the main() function
Code:
LED = ON;

will be replaced by the compiler with


Code:
RB0 = 1;

during the compilation process.


The sample program above doesnt really show the advantages of using macro name substitution except for
some fancy naming styles
. But in more complicated programs, this will improve the program readability,
portability, maintainability, as well as efficiency.
This is another way of connecting an LED to PIC...

The LED will turn on when the output pin is logic 0, off if logic high...

Our 6th Hello, LED program...


Code:
#include <pic.h>
//configuration bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT)
void main()
{
TRISB1 = 0;
RB1 = 0;

//RB0 is output
//Turn on LED

while(1);
}

I added a __CONFIG() statement before main() since i'm quite lazy in setting the config. bits in the
Configuration menu
Here is our next example, consisting of 2 LEDs and 1 pushbutton. The program will turn on an LED connected
at RB4 while another LED at RA1 will turn on only if the pushbutton connected at RB1 is pressed.

Figure 14. 2 LED + 1 pushbutton


Code:
#include <pic.h>
//config. bits
__CONFIG( XT & WDTDIS & PWRTDIS & UNPROTECT);
void main()
{
TRISB4 = 0;
RB4 = 1;

//RB4 is an output pin


//BLUE LED is on

TRISA1 = 0;
RA1 = 0;

//RA1 is an output pin


//RED LED is off

TRISB1 = 1;

//RB1 is an input pin

while(1)
{
if(RB1==0)
//if pushbutton is pressed, turn on RED LED
{
RA1 = 1;
}
else
//else, RED LED is OFF
RA1 = 0;
}
}

RB4 and RA1 are configured as output pins, while RB1 is an input pin. When the pushbutton is pressed, 0V
will appear at RB1 so that it will hold a logic 0. If the pushbutton is not pressed, +5V will appear since RB1 pin
is pulled up to +5V via R4 (10kohms).
Here is another code similar to our previous example. This code has a user-defined C Function.
Code:
#include <pic.h>
//configuration bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void init_PORTS(void)
{
TRISB4 = 0;
RB4 = 0;

//this is a user-defined function with a


//name init_PORTS
//RB4 is output
//LED1 is off

TRISA1 = 0;
RA1 = 0;

//RB1 output
//LED2 is off

TRISB1 = 1;

//RB1 is input

//after the last code in this function is executed, program execution


//will return to the next line in the main() function
}
void main()
{
init_PORTS();

RB4 = 1;

//call/invoke the user-defined function defined above


//and initialize RB4, RB1, and RA1
//turn on LED1

while(1)
//this
{
if (RB1==0)
RA1 = 1;
else
RA1 = 0;
}
}

is an infinite loop
//if pushbutton is pressed
//LED2 is on
//if pushbutton is NOT pressed
//LED2 is off

A C function is simply a self-contained block of C code. A function has a name, and you can call/invoke a function using
its name. When a function is called/invoked, the C code inside this function (that is, the function body!) is executed.
Why do we need to use functions?
1. Functions can be invoked anywhere in the program. Which means that a particular code (the code inside the
function) can be used again and again, resulting to a much smaller program size. Without functions, programs can grow
very, very large since you would need to replicate the same code again and again at several points in the program.
2. Using functions allow us to easily break a large program into manageable chunks. Thinks of functions as subprograms inside your C program.
In the above program, there is a function named init_PORTS(). The parenthesis after the function name is included to
denote that init_PORTS is a C function.
In the above program, the function is called (or invoked) at the start of main(). The function body (that is, the block of
code inside the function declaration) is executed when the function is called.

by now, the learner should have familiarize the ff:


- register names like TRISB, TRISA, PORTA, PORTB
- name of individual bits inside the registers, like RA1, RB0, TRISB7
- setting and clearing of bits using the AND and OR bitwise operators
- using user-defined C functions

Our next example is a "blinker" program (*my favorite

).

An LED connected at RB0 is turned on/off repeatedly (i.e. "blink")


ito po ang code:
Code:
#include <pic.h>
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void main()
{
TRISB0 = 0;
RB0 = 0;
while(1)
{
RB0 = 1;
RB0 = 0;
}
}

//RB0 is output
//LED is off

//LED is ON
//LED is OFF

There are two basic ways to create a delay:


(1) software delay using empty FOR/WHILE loops and
(2) hardware delays using the MCU timer peripherals
Software delay po muna ang gamitin natin. Much later na hardware delays (in the next few months

Using software-based delay (or software delay, for short), the idea is simply to make the CPU execute portion
of codes that actually does nothing other than to "waste" CPU execution times. The simplest way it to use an
empty For loop.
Code:
for(i=0;i<=0xFFFF;i++)
;
//empty body

In the above code, the FOR loop has an empty body (no statements). The FOR loop will simply cause the CPU
to count from 0 to 65535 (0xFFFF), then it exits.

Let's improve the previous code. I will put the FOR loop delay inside a function, named delay().
Then i will simply call the delay() function with a delay(); statement anywhere in main() .
Code:
#include <pic.h>
//configuration fuses
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void delay(void)
{
unsigned int i;
for(i=0;i<0xFFFF;i++)
;

//this is a software delay function

//FOR loop does nothing, just


//increment variable i from 0
//to 65535

void main()
{
TRISB &= ~0x01;
PORTB &= ~0x01;

//initialize RB0 pin as output


//LED is initially off

while(1)
{
PORTB |= 0x01;
delay();
PORTB &= ~0x01;
delay();
}
}

//infinite loop
//LED is on
//invoke delay() function
//LED is off
//invoke delay() function

This "blinker" program is a better program than the previous one, in terms of memory usage since there is only
1 FOR loop (inside the delay() function) that can be executed many times over. The previous "blinker" program
has 2 FOR loops inside the main() function.
You may check the total program memory space used during compilation. This "blinker" code consumed only
28 words (the smaller program memory used, the better), while the previous code has 33 words.
This sample program demonstrates the advantages of using C functions.

Let's improve the "blinker" program once again. In turning on/off the LED, we will use the ^ operator (XOR
bit-wise operator).
As a review, when a bit is XORed with 1, this bit will be toggled. For example, if this bit is 0, XORing it with 1
will changed its value to 1. If the the bit is 1, XORing will changed the value to 0
In summary,
0^1=1
1^1=0

Here is our 3rd "blinker" program:


Code:
#include <pic.h>
//configuration fuses
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void delay(void)
{
unsigned int i;
for(i=0;i<0xFFFF;i++)
;
}
void main()
{
TRISB &= ~0x01;
PORTB &= ~0x01;
while(1)
{
PORTB ^= 0x01;
delay();
}
}

//this is a delay function

//FOR loop does nothing, just


//increment variable i from 0
//to 65535

//initialize RB0 pin as output


//LED is initially off
//infinite loop
//Toggle RB0
//invoke delay() function

In the above program, RB0 bit (PORTB<0>) is toggled again and again. Since an LED is connected at RB0,
this LED will "blink"

Our 4th "blinker" program


There are 2 LEDs, one connected at RB0 and the other at RB2. The LEDs will blink alternate to each other..
Code:
#include <pic.h>
//configuration fuses
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void delay(void)
{
unsigned int i;
for(i=0;i<0xFFFF;i++)
;
}
void main()
{
TRISB &= ~0x05;
PORTB &= ~0x01;
PORTB |= 0x04;
while(1)
{
PORTB ^= 0x05;
delay();
}

//this is a delay function

//FOR loop does nothing, just


//increment variable i from 0
//to 65535

//RB0 and RB2 are output pins


//LED at RB0 is initially off
//LED at RB2 is initially on
//infinite loop
//Toggle RB0 and RB2
//invoke delay() function

Here is our next example program.


Lets combine the features of our previous sample programs, from turning on an LED, reading input from
pushbuttons, and using software delays, to make a more complicated program.
Refer to Figure 14. There are 3 pushbuttons (PBs) connected to PORTA<2:0> and 4 LEDs at PORTB<3:0>.
The PBs has reference names: START_PB connected at RA0, LED2_PB connected at RA1, and STOP_PB
connected at RA2.

Figure 14. 3 pushbuttons and 4 LEDs

When the PIC MCU is powered up, it will not "respond" until START_PB is pressed. LED1 (at RB0) will turn
on when START_PB is pressed, and LED3 and LED4 (at RB2 and RB3, respectively) will start blinking
alternate to each other. If LED2_PB is pressed, LED2 (at RB2) will turn on.
If STOP_PB is pressed, the PIC MCU stops responding to the pushbuttons and all the LEDs are off.
Here is the code:
Code:
#include <pic.h>
//configuration fuses
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void delay(void)
{
unsigned int i;
for(i=0;i<0x3FFF;i++)
;
}
void init_LEDS(void)

//this is a delay function

//FOR loop does nothing, just


//increment variable i from 0
//to 16382

{
TRISB &= ~0x0F;
PORTB &= ~0x0F;

//PORTB<3:0> are output pins


//All 4 LEDS are off

}
void init_PUSHBUTTONS(void)
{
TRISA |= 0x07;
}
void main()
{
init_LEDS();
init_PUSHBUTTONS();

//PORTA<2:0> are input pins

//initialize PORTB
//initialize PORTA

while(RA0==1)
;

//loop while START_PB is not pressed

RB0
RB1
RB2
RB3

//turn
//turn
//turn
//turn

=
=
=
=

1;
0;
0;
1;

while(1)
{
PORTB ^= 0x0C;

on LED1
off LED2
off LED3
on LED4

//Toggle RB2 and RB3, LED3 and LED4 will


//blink alternately

if (RA1==0)
RB1 = 1;
else
RB1 = 0;

//if LED2_PB is pressed


//LED2 is on
//if LED2_PB is not pressed
//LED2 is off

if (RA2==0)
break;

//if STOP_PB is pressed,


//exit from this WHILE loop

delay();

//invoke delay() function

}
PORTB &= ~0x0F;

//all LEDS are off

while(1);

//infinite loop
//you may press the RESET button
//to start all over again

When the STOP_PB is pressed, you may press the RESET button to reset the MCU program execution.

Another example on software-based delay (using the "blinker" program

The sample code below will demonstrate how to use a C function that accepts an argument.
Code:
#include <pic.h>
//config bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);

void delay(unsigned int delay_val)


variable
{
unsigned int i;

//this is a delay function that accepts a


//The user can send value to this function
//to control the duration of the delay

for(i=0;i<delay_val;i++)
;
}
void main()
{
unsigned int i;
PORTB &= ~0x01;
TRISB &= ~0x01;

//RB0 is low
//RB0 is output

while(1)
{
PORTB |= 0x01;
delay(65535);

//LED on
//call delay function, and pass value 65535

PORTB &= ~0x01;


delay(32767);

//LED off
//call delay function, and pass value 32767 (=

65535/2)
}
}

Compile and simulate this code. You will notice that the LED is on twice longer than when it is off.
The delay() function above accepts a value/argument when it is called. Try comparing the delay() code in the
previous "blinker" and the code in this "blinker" program.

Let's use a more "advanced" delay() function, but this time, using the sample code from Hi-Tech.
Browse the Hi-Tech C installation directory and go to ...\HT-PIC\samples\delay directory
There are 2 files, delay.c and delay.h, each with the following code:
delay.h
Code:
#ifndef XTAL_FREQ
#define XTAL_FREQ
#endif
#define MHZ
#define KHZ
#if

4MHZ

*1000L
*1

/* Crystal frequency in MHz */

/* number of kHz in a MHz */


/* number of kHz in a kHz */

XTAL_FREQ >= 12MHZ

#define DelayUs(x)

{ unsigned char _dcnt; \


_dcnt = (x)*((XTAL_FREQ)/(12MHZ)); \
while(--_dcnt != 0) \
continue; }

#else
#define DelayUs(x)

{ unsigned char _dcnt; \


_dcnt = (x)/((12MHZ)/(XTAL_FREQ))|1; \
while(--_dcnt != 0) \
continue; }

#endif
extern void DelayMs(unsigned char);

delay.c
Code:
#include

"delay.h"

void
DelayMs(unsigned char cnt)
{
#if
XTAL_FREQ <= 2MHZ
do {
DelayUs(996);
} while(--cnt);
#endif
#if

XTAL_FREQ > 2MHZ


unsigned char i;
do {
i = 4;
do {
DelayUs(250);
} while(--i);
} while(--cnt);

#endif
}

The codes above are rather more sophisticated, but are a lot more exact (in delay duration) and more easier to
use than the previous delay() functions we have so far..
In the two files above, delay.c and delay.h, there are two useful functions:
(1) DelayUs()
(2) DelayMs()
We will use the second function, to create a software delay in the range of 1 to 255 milliseconds..
Now, create a new "blinker" project using the PIC16F84a. Then, add to the project a main.c file with the
following code:
Code:
#include <pic.h>
#include "delay.c"
void main()
{
TRISB &= ~0x01;

PORTB &= ~0x01;


while(1)
{
PORTB ^= 0x01;
DelayMs(250);
}

//toggle RB0
//delay for 250 ms

Copy and paste the delay.c and delay.h file from the sample folder to the current project directory. In the
blinker project directory, you will now have 3 files:

main.c
delay.c
delay.h

Build the project, and simulate the blinker program in Proteus. The LED at RB0 will turn on/off repeatedly
every 0.5 seconds (250 ms off and 250 ms on).
The main.c program above is quite simple, though there are two lines of code that need a little explanation.
This line
Code:
#include "delay.c"

will include the "delay.c" file ( as well as the delay.h files, since the delay.c "includes" the delay.h) into the
main.c file. We need to add this file since we are going to use the function DelayMs() declared inside this file.
During the compilation process, the compiler will try locate the delay.c and delay.h file in the same directory as
the main.c. If the two files are not found, there will be a compilation error.

Lastly, the line


Code:
DelayMs(250);

inside the while(1) loop will invoke the DelayMs() function that is inside the delay.c file, and pass to it a value
of 250. The DelayMs() will then execute a delay for 250ms.
Conclusion on the above example.
The DelayMs() (as well as the DelayUs() ) function is accurate in creating a delay, compared to our previous
delay() functions. I suggest using this sample code for timing-sensitive programs that uses software delay().
The example above also assumes that the PIC mcu is using a 4 Mhz crystal. If you will use a different crystal
oscillator, you will need to modify the delay.c file.
For example, if you want to use 1 Mhz, open the delay.h file and replace the following code
Code:

#ifndef XTAL_FREQ
#define XTAL_FREQ
#endif

4MHZ

/* Crystal frequency in MHz */

1MHZ

/* Crystal frequency in MHz */

with this code


Code:
#ifndef XTAL_FREQ
#define XTAL_FREQ
#endif

Lastly, if you want a delay longer than 255 ms, say 1 second, use this code instead
Code:
.
.
//1 second delay
DelayMs(250);
DelayMs(250);
DelayMs(250);
DelayMs(250);
.
.

Our next sample program will display the binary 0 (0x00) to 15 (0x0F) to 4 LEDs connected to PORTB<3:0>.
The LED at RB0 represents the least significant bit and the LED at RB3 is the most significant bit. When all
LEDs are off, this correspond to 0 and when all LEDs are on, this correspond to binary 15. There will be a 1
second delay in between values.
Here is the proteus screenshot:

(^ the pic display the binary 5)

and the program:


Code:
//4 LEDs at PORTB<3:0>
#include <pic.h>
#include "delay.c"

//delay.c file from hi-tech sample folder

//CONFIG bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
//initialize PORTB
void init_LED(void)
{
PORTB &= ~0x0F;
TRISB &= ~0x0F;
}
//1 second delay
void delay(void)
{
DelayMs(250);
DelayMs(250);
DelayMs(250);
DelayMs(250);
}
void main()
{
init_LED();
while(1){
PORTB = 0x00;
delay();
PORTB = 0x01;
delay();
PORTB = 0x02;
delay();
PORTB = 0x03;
delay();
PORTB = 0x04;
delay();
PORTB = 0x05;
delay();
PORTB = 0x06;
delay();
PORTB = 0x07;
delay();
PORTB = 0x08;
delay();
PORTB = 0x09;
delay();
PORTB = 0x0A;
delay();
PORTB = 0x0B;
delay();
PORTB = 0x0C;
delay();
PORTB = 0x0D;
delay();

//PORTB<3:0> are output

//display 0
//display 1
//display 2
//display 3
//display 4
//display 5
//display 6
//display 7
//display 8
//display 9
//display 10
//display 11
//display 12
//display 13

PORTB = 0x0E;
delay();
PORTB = 0x0F;
delay();

//display 14
//display 15

}
}

In the program, we use the software delay function defined in "delay.c" file from the Hi-tech C sample folder.
Make sure that you copy the delay.c and delay.h file into the project folder before building the program.
Let's improve the previous sample program. We will replace the "hard-coded" 0-15 values, but instead use a
variable. This will result to a smaller program in terms of size.
In the main() program, we will declare an unsigned char variable named value. We will send this value to
PORTB, to be displayed by the 4 LEDs (representing the bit locations) for 1 second. Then value will be
incremented by 1 and displayed again, starting from 0 (0x00) to 15 (0x0F). If value is more than 15, it will be
reset back to 0.
Code:
//4 LEDs at PORTB<3:0>
#include <pic.h>
#include "delay.c"

//delay.c file from hi-tech sample folder

//CONFIG bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
//initialize PORTB
void init_LED(void)
{
PORTB &= ~0x0F;
TRISB &= ~0x0F;
}
//1 second delay
void delay(void)
{
DelayMs(250);
DelayMs(250);
DelayMs(250);
DelayMs(250);
}
void main()
{
unsigned char value = 0x00;
init_LED();

//PORTB<3:0> are output

while(1){
PORTB = value;
//display value, LEDs will turn on/off,
corresponding to the binary value of the variable
value++;
//increment value by 1
if (value > 0x0F)
//if value is more than 15
value = 0x00; //restart back to 0

delay();

//1 second delay

}
}

This program is 57 words in size, the previous program about 89 words.


To display 8-bit binary values to PORTB, dagdagan lang ng additional LEDs on PORTB<7:4>.
Tapos i-simulate nyo with the previous program above without the If statement. It will count from 0-255-0-255.... Also, don't forget to configure the RB7-RB4 pins as output pins
You might want to reduce the actual delay also, para hindi kayo maiinip sa kakahintay when waiting for the
LEDS to display 255

Figure. 15. PIC16F84a + 8 LEDs at PORB


ito na next sample program natin, the "scrowling LED" project.
Magconnect lang tayo ng 8 LEDs sa PORTB (Figure 15 above). Then we will simply turn on the LED one after
the other, from right to left then right again, and so on. Figure 16 below is the actual simulation

FIGURE 16. Scrowling LED simulation


Hi-Tech C code:
Code:
//8 LEDs at PORTB
#include <pic.h>
#include "delay.c"

//delay.c file from hi-tech sample folder

//CONFIG bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
//initialize PORTB
void init_LED(void)
{
PORTB &= ~0xFF;
TRISB &= ~0xFF;
}
//0.5 second delay
void delay(void)
{
DelayMs(250);
DelayMs(250);
}
void main()
{
unsigned char value = 0x00;
init_LED();
while(1){
PORTB = 0x01;
delay();
PORTB = 0x02;
delay();
PORTB = 0x04;
delay();
PORTB = 0x08;
delay();

//PORTB<3:0> are output

//display value 0000 0001


//display value 0000 0010
//display value 0000 0100
//display value 0000 1000

PORTB = 0x10;
delay();
PORTB = 0x20;
delay();
PORTB = 0x40;
delay();
PORTB = 0x80;
delay();
PORTB = 0x40;
delay();
PORTB = 0x20;
delay();
PORTB = 0x10;
delay();
PORTB = 0x08;
delay();
PORTB = 0x04;
delay();
PORTB = 0x02;
delay();
PORTB = 0x01;
delay();

//display value 0001 0000


//display value 0010 0000
//display value 0100 0000
//display value 1000 0000
//display value 0100 0000
//display value 0010 0000
//display value 0001 0000
//display value 0000 1000
//display value 0000 0100
//display value 0000 0010
//display value 0000 0001

}
}

The delay is 0.5 seconds. You might want to change it to around 50 ms, para mas enjoy yung simulation

and the CCS C code:


Code:
#include <16F84A.h>
#FUSES
#FUSES
#FUSES
#FUSES

NOWDT
XT
NOPUT
NOPROTECT

//No Watch Dog Timer


//Crystal osc <= 4mhz
//No Power Up Timer
//Code not protected from reading

#use delay(clock=4000000)
void main()
{
set_tris_b(0x00);
output_b(0x00);
while(1)
{
output_b(0x01);
delay_ms(500);
output_b(0x02);
delay_ms(500);
output_b(0x04);
delay_ms(500);
output_b(0x08);
delay_ms(500);
output_b(0x10);
delay_ms(500);
output_b(0x20);

//PORTB are output


//all LEDs are off

delay_ms(500);
output_b(0x40);
delay_ms(500);
output_b(0x80);
delay_ms(500);
output_b(0x40);
delay_ms(500);
output_b(0x20);
delay_ms(500);
output_b(0x10);
delay_ms(500);
output_b(0x08);
delay_ms(500);
output_b(0x04);
delay_ms(500);
output_b(0x02);
delay_ms(500);
output_b(0x01);
delay_ms(500);
}
}

and ito na po ang next "scrowling LED" program, using shift left (<<) and shift right (>>) operators
Code:
//8 LEDs at PORTB
#include <pic.h>
#include "delay.c"

//delay.c file from hi-tech sample folder

//CONFIG bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
#define SHIFT_RIGHT
#define SHIFT_LEFT

0x00
0x01

//initialize PORTB
void init_LED(void)
{
PORTB &= ~0xFF;
TRISB &= ~0xFF;
}
//0.5 second delay
void delay(void)
{
DelayMs(250);
DelayMs(250);
}
void main()
{
unsigned char value = 0x01;
unsigned char direction;
init_LED();

//PORTB are output

direction = SHIFT_LEFT;
while(1)
{
//RB0 on --> RB1 --> .... RB7
if (direction == SHIFT_LEFT)
{
PORTB = value;
value = value << 1;
if (value == 0x80)
displayed is 0x80
direction = SHIFT_RIGHT;
delay();

//display to PORTB
//shift left
//check if next value to be
//reverse direction

}
//RB7 on --> RB6 --> .... RB0
if (direction == SHIFT_RIGHT)
{
PORTB = value;
value = value >> 1;
if (value == 0x01)
displayed is 0x01
direction = SHIFT_LEFT;
delay();
}
}
}

//display to PORTB
//shift right
//check if next value to be
//reverse direction

This is our 3rd "scrowling LED" program. The LED is not drive directly by the PORTB pins. The LED will
turn on when the output pin is pulled low to ground level.

Figure 17. The LED anode are pulled up to +5V via 330 resistor,
anode to PORTB pins

In the last sample program, just modify this line


Code:

PORTB = value;

//display to PORTB

to this
Code:
PORTB = ~value;

//invert all bits in this variable, then display to PORTB

Next topic is 7-segment interfacing. 7-segments are simply LEDs arrange in a way that they form single digit
numbers. The digit 0-9 can be displayed by turning on the corresponding LEDs (or segments).
There are 2 kinds of LEDs: common anode and common-cathode.

Figure 18. 7-segment package

Figure 19. Common cathode and common anode 7-segments

Each LED (or segments) are named/referenced as shown below

Figure 20. The are 7 LEDs (segments)


named a to g
If we want to display the number 1, we simply turn on segment b and c.
If we want to display the number 8, then all segments must be on.

In our next example, we will use the common-anode 7-segment.


In this example, we will display 0-9 repeatedly to the common-anode 7-segment. There will be 0.5 second delay
between each display.
Here is the sample code.
Code:
//common-anode 7-segment at PORB<6:0>
#include <pic.h>
#include "delay.c"
//CONFIG bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void delay(void)
{
DelayMs(250);
DelayMs(250);
//
DelayMs(250);
//
DelayMs(250);
}
void init_7SEGMENT(void)
{
PORTB &= ~0x7F;
TRISB &= ~0x7F;
}
void main()
{
init_7SEGMENT();
while(1)
{
PORTB = ~0x3F;
delay();
PORTB = ~0x06;
delay();

//PORTB<6:0> are output pins

//0
//1

PORTB = ~0x5B;
delay();
PORTB = ~0x4F;
delay();
PORTB = ~0x66;
delay();
PORTB = ~0x6D;
delay();
PORTB = ~0x7D;
delay();
PORTB = ~0x07;
delay();
PORTB = ~0xFF;
delay();
PORTB = ~0x6F;
delay();

//2
//3
//4
//5
//6
//7
//8
//9

}
}

and the simulation.

Figure 21. Common anode 7-segment. Count from 0 to 9.


The circuit in Figure 21 is similar to Figure 17, except that there is no LED connected at RB7 pin (there are 7
led/segments in a 7-segment, remember that
). The segments a to g are connected to RB0 to RB6,
respectively. The (common) anode of the LEDs are connected to +5V.
Remember that for a segment to turn on, the RBx pin must be low (at GND potential).

ito naman ang common-cathode seven-segment

Figure 22. Interfacing common cathode 7-segment to PICmicro.


Count from 0 to 9.

In the previous program, simply remove the ~ operators inside the while(1) loop, then build and simulate the
application in Proteus. Segments a to g are connected to RB0 to RB7, respectively. The (common) cathode of
the segments is connected to ground.
This circuit is similar to Figure 15, without the LED at RB7
Here is the seven-segment program using Switch statement
Code:
//common-anode 7-segment at PORB<6:0>
#include <pic.h>
#include "delay.c"
//CONFIG bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void delay(void)
{
DelayMs(250);
DelayMs(250);
//
DelayMs(250);
//
DelayMs(250);
}
void init_7SEGMENT(void)
{

PORTB &= ~0x7F;


TRISB &= ~0x7F;
}
void main()
{
unsigned char value = 0x00;
init_7SEGMENT();
while(1)
{
switch(value)
{
case 0x00:
PORTB =
break;
case 0x01:
PORTB =
break;
case 0x02:
PORTB =
break;
case 0x03:
PORTB =
break;
case 4:
PORTB =
break;
case 5:
PORTB =
break;
case 6:
PORTB =
break;
case 7:
PORTB =
break;
case 8:
PORTB =
break;
case 9:
PORTB =
break;
}

//PORTB<6:0> are output pins

~0x3F;

//0

~0x06;

//1

~0x5B;

//2

~0x4F;

//3

~0x66;

//4

~0x6D;

//5

~0x7D;

//6

~0x07;

//7

~0xFF;

//8

~0x6F;

//9

value++;

//increment value

if (value > 0x09)


value = 0x00;

//if value is more than 9,


//reset value back to 0

delay();

//0.5 seconds delay

}
}

Same result as in Figure 21.


This is another example on interfacing 7-segments to PIC using a 74LS47 BCD-to-7segment decoder/driver
IC. The advantages of using this IC is that it will result to a more simple program and requires less I/O pin on
the mcu (4 output pins only). The obvious disadvantage is the extra cost of the chip

Here is the sample code.


Code:
//common-anode 7-segment at PORB<3:0>
#include <pic.h>
#include "delay.c"
//CONFIG bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void delay(void)
{
DelayMs(250);
DelayMs(250);
}
void init_7SEGMENT(void)
{
PORTB &= ~0x0F;
TRISB &= ~0x0F;
}
void main()
{
unsigned char value = 0x00;
init_7SEGMENT();

//PORTB<3:0> are output pins

while(1)
{
PORTB = value;
value++;

//send value to 7-segment driver


//increment value

}
}

and the simulation

if (value > 0x09)


value = 0x00;

//if value is more than 9,


//reset value back to 0

delay();

//0.5 seconds delay

Figure 23. 7-segment with 74LS47

The 74LS47 can only be used with a common anode 7-segment.


Next program natin ay magdisplay ng number sa dalawang 7-segments.

Figure 24. PIC + dual 7-segment display

The circuit above shows 2 common anode 7-segment displays sharing the pin connections (multiplexed) from
the 74LS47 IC. SEGMENT1 and SEGMENT2 can be disabled/enabled via the NPN transistors Q1 and Q2,
respectively. Q1 will conduct (and enable SEGMENT1) if RA0 is high, and Q2 will conduct (and enable
SEGMENT2) if RA1 is high
If we want to display the number "41", all we have to do is output '1' to PORTB<3:0>, enable SEGMENT1
(RA0 = 1) and disable SEGMENT2 (RA1=0). Then output '4' to PORTB<3:0>, enable SEGMENT2 (RA1 = 1)
and disable SEGMENT1 (RA0 = 0). We repeat the process ad infinitum.
Here is the sample code.
Code:
//PORTB<3:0> to 74LS47
//RA0 enable/disable SEGMENT1
//RA1 enable/disable SEGMENT2
#include <pic.h>
#include "delay.c"
//function prototype
void delay(void);
void main()
{
PORTB
TRISB
PORTB
TRISA

&=
&=
&=
&=

~0x0F;
~0x0F;
~0x03;
~0x03;

//PORTB<3:0> are output


//PORTA<1:0> are output

while(1)
{
PORTA = 0x01;
PORTB = 0x01;
delay();

//enable SEGMENT1, disable SEGMENT2


//display '1'
//500ms delay

PORTA = 0x02;
PORTB = 0x04;
delay();

//enable SEGMENT2, disable SEGMENT1


//display '4'
//500ms delay

}
}
void delay(void)
{
DelayMs(250);
DelayMs(250);
}

There is a 0.5 second delay between each display.


ito na ang simulation

Figure 25. PIC + dual 7-segment display: simulation with 0.5 seconds delay
Remember that only 1 7-segment should be on at any time, since they are sharing the same 74LS47 data pins,
else they will display the same digit.
In the previous program, replace the 500 ms second delay with a 50 ms delay then re-simulate. This is the
fastest possible simulation in Proteus that will display the digits properly (at least as seen on my PC, and its not
exactly real-time
)
In an actual hardware, the delay should be chosen that result to at least 30x display per second. This will result
to a smooth display and the 2 digits are now seen as on at the same time, since the 2 7-segment are alternately
switched on rapidly.
The human eye cant notice the "blinking" of the LEDs at frequencies above 16 Hz, so 30 Hz is already a good
choice. You can experiment further.

Figure 26. PIC + dual 7-segment display: simulation with 0.5 seconds delay
This is an improved sample program of the previous one
Code:
//PORTB<3:0> to 74LS47
//RA0 enable/disable SEGMENT1
//RA1 enable/disable SEGMENT2
#include <pic.h>
#include "delay.c"
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
//macro definitions, used for easier code readability, etc..
#define ENABLE_SEGMENT1
(PORTA=0x01)
#define ENABLE_SEGMENT2
(PORTA=0x02)
//function prototype
void delay(void);
void main()
{
unsigned char display_value = 41;
unsigned char tens_digit;
unsigned char ones_digit;

//number to be displayed
//holds the tens digit of display_value
//holds the ones digit of display_value

PORTB &= ~0x0F;


TRISB &= ~0x0F;

//PORTB<3:0> are output pins

PORTA &= ~0x03;


TRISA &= ~0x03;

//PORTA<1:0> are output pins

tens_digit = display_value/10;
ones_digit = display_value - (tens_digit * 10);

//get the tens digits, = 4


//get the ones digit, = 1

while(1)
{
ENABLE_SEGMENT1;
PORTB = ones_digit;
delay();

//display '1'
//500ms delay

ENABLE_SEGMENT2;
PORTB = tens_digit;
delay();

//display '4'
//500ms delay

}
}
void delay(void)
{
DelayMs(250);
DelayMs(250);
}

try to compare this program and the previous one.


I included a simple equation for obtaining the digit in the number
Code:
tens_digit = display_value/10;
ones_digit = display_value - (tens_digit * 10);

//get the tens digits, = 4


//get the ones digit, = 1

instead of hard-coding the digits like in the previous program.

I also added a function prototype for delay() function.


Code:
//function prototype
void delay(void);

Up to this time, we usually define our user-defined functions before the main() function. If you will define
your functions below/after the main() function, you need to include a function prototype before main() so that
the compiler will not flag a compile error when it encounters the function name inside the main().
I also included macros, for some fancy naming conventions and improve readbility (it has more uses actually
)
Code:
#define ENABLE_SEGMENT1
#define ENABLE_SEGMENT2

(PORTA=0x01)
(PORTA=0x02)

It is recommended to ALWAYS use capital letters for MACRO names.


this next program will display 0-99
Code:

//PORTB<3:0> to 74LS47
//RA0 enable/disable SEGMENT1
//RA1 enable/disable SEGMENT2
#include <pic.h>
#include "delay.c"
//configuration fuse bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
//macro definitions
#define ENABLE_SEGMENT1
#define ENABLE_SEGMENT2

(PORTA=0x01)
(PORTA=0x02)

//function prototype
void delay(void);
void main()
{
unsigned char display_value = 0x00;
displayed to 7-segment
unsigned char tens_digit;
unsigned char ones_digit;

//stores any value between 0-99, value to be


//stores the tens digit of display_value
//stores the ones digit of display_value

PORTB &= ~0x0F;


TRISB &= ~0x0F;

//PORTB<3:0> are output

PORTA &= ~0x03;


TRISA &= ~0x03;

//PORTA<1:0> are output

while(1)
{
tens_digit = display_value/10;
ones_digit = display_value - (tens_digit * 10);

//tens digit
//ones digit

ENABLE_SEGMENT1;
PORTB = ones_digit;
delay();

//enable SEGMENT1
//display ones digit
//250ms delay

ENABLE_SEGMENT2;
PORTB = tens_digit;

//enable SEGMENT2
//display tens digit

display_value++;

//increment number, 0-99

if (display_value > 99)


display_value = 0x00;
delay();
}
}
void delay(void)
{
DelayMs(250);
}

Here is the simulation.

//if more than 99


//reset back to 0
//250ms delay

Figure 27. Counts from 0 to 99 (simulation shows up to 29 only)

NOTE: The previous programs are not fully working (usable on an actual application), i included them here to
demonstrate the basic concepts like enabling/disabling segments and extracting digits for display
Here is another more improved program. The program will still count from 0 to 99, with approximately 1
second per update.
Code:
//PORTB<3:0> to 74LS47
//RA0 enable/disable SEGMENT1
//RA1 enable/disable SEGMENT2
#include <pic.h>
#include "delay.c"
//configuration fuse bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
//global variables
unsigned char display_value = 0x00;
displayed to 7-segment
unsigned char tens_digit;
unsigned char ones_digit;

//stores any value between 0-99, value to be


//stores the tens digit of display_value
//stores the ones digit of display_value

//function prototypes
void update_value(unsigned char display_value);
void display(void);
void main()
{
unsigned char temp=0x00;

PORTB &= ~0x0F;


TRISB &= ~0x0F;

//PORTB<3:0> are output

PORTB &= ~0x03;


TRISA &= ~0x03;

//PORTA<1:0> are output

update_value(display_value);

//initial update

while(1)
{
display();
segments

//display display_value to 7-

temp++;
if (temp == 9)

//when temp==9 ,approx. 1 second has

elapsed
{
display_value++;

//increment value

if (display_value > 99)


display_value = 0x00;

//if more than 99


//reset back to 0

update_value(display_value);

//extract tens/ones digit of new

value
temp = 0x00;
}
}
}
//display to 7segment
void display(void)
{
PORTA = 0x01;
PORTB = ones_digit;
DelayMs(50);

//enable SEGMENT1
//display '1'
//50ms delay

PORTA = 0x02;
PORTB = tens_digit;
DelayMs(50);

//enable SEGMENT2
//display '4'
//50ms delay

}
//extract the tens and ones digit
void update_value(unsigned char display_value)
{
tens_digit = display_value/10;
ones_digit = display_value - (tens_digit * 10);
}

The switching between the 2 segments is now very rapid and will not display appropriately on a GIF figure, so i
will not post a GIF image.
Build the program and simulate in Proteus.
You may now want to test this program on an actual hardware. To "smoothen" (eliminate noticeable flicker) the
display, just reduce the DelayMs() argument inside the display() function. To control the time between

increment of display_value just change the limit of the temp variable inside the while(1) loop. (ie. if (temp ==
18))

Sample code ng pointer in pic serial transmission


Code:
void puts(const unsigned char *s)
{
while(*s)
{
while(!TXIF)
;
TXREG = *s;
send automatically
s++;
}
}

//while end of string is not reached

//wait while previous char is being transmitted


//send character to UART transmitter register, char
//point to next character

sample use:
Code:
void main()
{
//initialization codes here
init_uart();
while(1)
{
puts("sevenstring impakta\r");
DelayMs(250);
}

//send string every 1/4 seconds

same function as above


Code:
void puts(const unsigned char *s)
{
while(*s++)
{
while(!TXIF)
;
TXREG = *s;
}
}

//while end of string is not reached

//wait while previous char is being transmitted


//send character to UART transmitter register

Example program. It will toggle an LED at RB0 everytime character 'a' or 'A' is receive
Code:
#include <pic.h>
void interrupt isr(void)
{
unsigned char c;
if (RCIF)
{
c = RCREG;
if (c == 'a' || c == 'A' )
RB0 ^= 1;

//read Receive buffer


//if 'a' or 'A'
//toggle LED

if(OERR)
{
CREN = 0;
CREN = 1;
}

//clear error if there is any

}
}

Pa'no kung "string" ang inaabangan instead of single "character" lang gaya nito?
im using the circular fifo para dito.
ito yung function na ginagamit
Code:
unsigned char * ser_gets(void)
{
unsigned char s[16];
unsigned char index = 0x00;
while(rxoptr!=rxiptr)
{
s[index] = ser_getch();
index++
}
while ();
return s;
}

Why use interrupts?


In order to understand the simple concept of microcontroller INTERRUPTS, let me give you a wonderfully brilliant
analogy.

Today, you are expecting a visitor. This visitor called you up the day before and told you that he will drop by at your
house to talk about a few things. However, he forgot to tell you what time he will come.
If you are really expecting this person to come any moment, you might keep on glancing at the window many times
during the hour. Or you might totally stop doing house chores and decided to just stand near the window to wait for the
visitor, which could arrived any moment or maybe after the next 4 hours. Now this is rather silly, isnt it? You might ask
me if there is a better way to wait for a house visitor than polling/checking/looking at the window.
I know, I know. The best thing you would rather do is to keep on working while waiting for the (*drums!) (tada!) DOOR
BELL to ring.
Now, this wonderful invention, the door bell (an interrupting mechanism) allows you to be INTERRUPTED by a visitor
standing outside your gate. Genius, right?
So you have two ways of knowing when the visitor arrives:
(1) polling (waiting/glancing for hours at the window
) and
(2) interrupts (using doorbell) (for the sake of discussion, lets assume you can't do both. If you insist you can, i'll force you to explain WHY there is GRAVITY
)

The same concept applies to microcontroller operation.


In the silly analogy above, think of the house as the microcontroller, you as the CPU (that is inside the microcontroller),
the visitor as an interrupt source (An interrupt source could be a changed logic signal at a particular input pin, a timer
that finished counting, a character arriving at a communication port, etc.), and the wonderful door bell as an interrupt
controller (the visitor interrupts the doorbell, which in turns interrupts you. Or you can also view the door bell as the interrupt source, whatever. You can see it that
way. Hopefully you get the point already, if not read again. Or the read again the whole thread hehe.)

Using interrupt in your microcontroller programs has lots of benefits. Mainly, it allows you to momentarily stop main()
program execution and let the CPU execute a special function instead that responds specifically to the interrupt (this
function is called an Interrupt Service Routine, ISR). This is one of the fundamentals to MCU multitasking, and greatly
expands the capability of the program and improve performance. With interrupts, you can turn on an LED when a
button is pressed, at the same time wait for a character being transmitted from PC to the MCU, or wait until the ADC
finishes acquiring digital values of analog signal, or typing a response to a thread in E-lab.ph forum, or nuke a city
somewhere in Africa. You feel powerful if you know everything about interrupts hehe
.

Simply put, using interrupts result to a more efficient program since precious CPU execution time is not wasted on
polling. The CPU will only respond to interrupts after it occurs.

The PIC16F84A has four interrupt sources


when Timer0 (TMR0) timer overflows (i.e. finished counting)
a signal transition in RB0/INT pin
signal transition in PORTB<7:4>
when data eeprom write is completed

In our next several examples, we will demonstrate how to use TMR0 as an interrupt source.

You will learn the following:


1. what is TMR0
2. how to initialize TMR0
- know the special function registers (SFRs) that control TMR0 operation
- determine the TMR0 reload/overflow period
- understand what is a prescaler and why is it used.
3. write an ISR for TMR0
4. other basic techniques in using TMR0 interrupt

The PIC16F84a has an 8-bit timer module/peripheral, called TIMER0 (or TMR0).
We can view the TMR0 simply as special kind of register and it can hold values from 0 to 255 (since it is 8-bit wide, 2 ^ 8
= 256).

When TMR0 is initialized properly in Timer mode, it can hold a starting value of 0 and increment every instruction clock
cycle (1 instruction clock cycle = Fosc/4) up to 255, where it then reloads/overflows back to 0 and start
incrementing/counting all over again until power is removed from the PIC or when TMR0 is disabled via software. When
TMR0 overflows, it can generate/trigger an interrupt to notify the CPU. The CPU will then execute a special code (called
an interrupt service routine) in response to this interrupt.

As an example, TMR0 will count from 0 - 255 every 0.256 ms when the PIC16F84a has a 4Mhz crystal oscillator.
(system clock)
and

(instruction clock)
also
(1 instruction clock period)
Therefore,

The TMR0 is useful in applications that requires timing reference. For example, you might want to read an IR sensor that
is connected at RB0 pin, say every 10 millisecond. You may then configure TMR0 to overflows every 10 millisecond and
also configure it as an interrupt source. When the TMR0 overflows, it will trigger an interrupt. The CPU will then respond
to this interrupt by executing a code (also known as interrupt service routine, ISR) that reads the RB0 pin.
Remember that TMR0 increments independent of the main program running in the PIC MCU. The program simply has to
initialize TMR0 at the start and respond to it occasionally via the ISR when the TMR0 interrupt triggers.
TMR0 is configured via the INTCON and OPTION special function registers. The bits relevant to TMR0 operation are
highlighted below.

Let's create a sample program that will demonstrate how to use the TMR0 as an interrupt source. This program
is rather simple. It will simply toggle (turn on/off) an LED connected at RB0 pin every time TMR0 overflow
every 0.256 ms. Below is the sample program.
Hi-Tech C Code:

Code:
#include <pic.h>
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);

//this is the interrupt service routine (or ISR)


void interrupt tmr0_isr(void)
{
PORTB ^= 0x01;
//toggle LED
INTCON &= ~0x04;;
//clear Timer0 interrupt flag (T0IF = 0)
}

void main()
{
//initialize RB0
TRISB &= ~0x01;
PORTB &= ~0x01;

//RB0 pin is output


//LED is off

//initialize TMR0
INTCON &= ~0x80;
OPTION &= ~0x20;
OPTION |= 0x08;

//disable all interrupts (GIE = 0)


//TMR0 uses the internal clock, Fosc/4 (T0CS = 0)
//prescaler is assigned to the Watch Dog Timer (PSA = 1)
//The WDT is disabled, however.
//If prescaler is assigned to WDT, then TMR0 will

increment
//EVERY clock cycle

INTCON &= ~0x04;


INTCON |= 0x20;
INTCON |= 0x80;

while(1);

//clear any previous/pending TMR0 interrupt (T0IF = 0)


//enable TMR0 interrupt (T0IE = 1)
//enable all interrupts (GIE = 1)

//infinite loop
//do nothing other than to wait for the TMR0 interrupt
//to occur, then execute the ISR (tmr0_isr)

Let's dissect the sample program.


The code
Code:
void interrupt tmr0_isr(void)
{
PORTB ^= 0x01;
INTCON &= ~0x04;
}

//toggle LED
//clear Timer0 interrupt flag

is the interrupt service routine (ISR for short). It is simply a C function with the keyword interrupt in the
function header. The function name can be any name, though it should be descriptive. Using the name tmr0_isr
is an obvious choice. There are two lines inside the ISR. The first line will toggle the RB0 pin. The second line
will clear the TMR0 interrupt flag (T0IF = 0). Remember that when the TMR0 interrupt is triggered, the T0IF
bit is set so at the end of the ISR it should also be cleared.
Unlike ordinary function that are executed via explicit function calls from the program, the ISR is executed
automatically (or invoked by the interrupt controller) when the interrupt triggers.

Next, inside the main() function we have


Code:
//initialize RB0
TRISB &= ~0x01;
PORTB &= ~0x01;

//RB0 pin is output


//LED is off

This will configure RB0 as an output pin. An LED is connected to RB0.

Next, we have these lines of code that will initialize TMR0.


Code:
//initialize TMR0
INTCON &= ~0x80;
OPTION &= ~0x20;
OPTION |= 0x08;

//disable all interrupts (GIE = 0)


//TMR0 uses the internal clock, Fosc/4 (T0CS = 0)
//prescaler is assigned to the Watch Dog Timer (PSA

TMR0 = 0x00;
INTCON &= ~0x04;

//The WDT is disable, however.


//TMR0 starts at zero.
//clear any previous/pending TMR0 interrupt (T0IF =

INTCON |= 0x20;
INTCON |= 0x80;

//enable TMR0 interrupt (T0IE = 1)


//enable all interrupts (GIE = 1)

= 1)

0)

We must set the appropriate values of the bits in the INTCON and OPTION registers.
It is advisable to disable triggering of all interrupts when we are configuring or initializing an interrupt source
like the TMR0. So we have the line
Code:
INTCON &= ~0x80;

This will clear the GIE bit (INTCON<7> bit) and will disable all interrupts.
The next line
Code:
OPTION &= ~0x20;

will select the instruction cycle clock (Fosc/4) as the input to TMR0. In other words, clearing the TOCS bit
(OPTION<5> bit) will select Fosc/4 as the clock source for the TMR0. When the T0CS bit is 0, TMR0 is said
to be in Timer mode. If T0CS is 1, TMR0 is in Counter mode. We are using Timer mode in this sample
program.

This line
Code:
OPTION |= 0x08;

will assign the prescaler to the Watch Dog Timer of the PIC.
The above line means that the TMR0 will increment EVERY clock cycle since we are not using the prescaler.
Take note in advance that it is possible to increment the TMR0 every 2,4,8,..256 clock cycles by choosing the

appropriate prescaler values in the OPTION register. By using prescaler, we can increase the TMR0 overflow
period (i.e. slow down the TMR0 overflow period). We will have examples on using TMR0 with prescaler
later.

The next line


Code:
TMR0 = 0x00;

will reset TMR0. This line is optional. If the TMR0 register is not initialize it will have any random value
between 0-255, which means that the TMR0 will overflow in less than 0.256 ms the first time, since it will
count from x - 255, where x is any random value at start up. The next time, it will now start at 0.

This line
Code:
INTCON &= ~0x04;

//clear any previous/pending TMR0 interrupt (T0IF = 0)

will clear the TMR0 interrupt flag, T0IF (INTCON<2> bit).


and the next line
Code:
INTCON |= 0x20;

will enable the TMR0 interrupt by setting the T0IE bit in the INTCON register (INTCON<5> bit).

This line
Code:
INTCON |= 0x80;

will set the global enable interrupt bit (GIE = 1). This will enable all (global) interrupts to trigger (though we
only have one interrupt source at this time). When GIE is set, the MCU will execute the ISR every time an
interrupt trigger.

The last line in the main() function is the infinite loop


Code:

while(1);

At this time, the MCU does nothing other than to wait for the ISR to execute when the TMR0 overflows.
When the TMR0 interrupt occur, the T0IF bit is set (this is how the CPU knows what kind of interrupt triggered), and the Program
Counter will then contain the starting address of the ISR (address 0x04). This automatically happen in
hardware and the CPU will execute the ISR.
The T0IF must be cleared in software before exiting the ISR; that is why we have
Code:
INTCON &= ~0x04;

as the last line in the ISR. When the ISR is finished, the CPU will resume back to the last address it was
executing next where it was interrupted.
As another example, we can write the previous sample program this way, using the bit names instead of the
register names.
Code:
#include <pic.h>
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);

//this is the interrupt service routine


void interrupt ISR(void)
{
PORTB ^= 0x01;
//or
//RB0 ^= 1;
T0IF = 0;

//toggle LED

//clear TMR0 interrupt flag

void main()
{
//initialize RB0
TRISB &= ~0x01;
RB0 = 0;

//RB0 pin is output


//LED is off

//initialize TMR0
GIE = 0;
T0CS = 0;
PSA = 1;

//disable all interrupts


//TMR0 uses the internal clock, Fosc/4
//prescaler is assigned to WDT

T0IF = 0;
T0IE = 1;
GIE = 1;

//clear any previous/pending TMR0 interrupt


//enable TMR0 interrupt
//enable all interrupts

while(1);

//infinite loop
//do nothing

Or we can also use user-defined functions like init_TMR0() below


Code:
#include <pic.h>
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);

//this is the interrupt service routine


void interrupt ISR(void)
{
PORTB ^= 0x01;
//or
//RB0 ^= 1;
T0IF = 0;

//toggle LED

//clear TMR0 interrupt flag

}
void init_TMR0(void)
{
GIE = 0;
T0CS = 0;
PSA = 1;

//disable all interrupts


//TMR0 uses the internal clock, Fosc/4
//prescaler is assigned to WDT

T0IF = 0;
T0IE = 1;
GIE = 1;

//clear any previous/pending TMR0 interrupt


//enable TMR0 interrupt
//enable all interrupts

void main()
{
//initialize RB0
TRISB &= ~0x01;
RB0 = 0;

//RB0 pin is output


//LED is off

//initialize TMR0
init_TMR0();
while(1);

//infinite loop
//do nothing

here is the equivalent code in CCS C


Code:
#include <16f84a.h>
#fuses XT, NOWDT, NOPUT, NOPROTECT

//configuration fuses

#use delay (clock = 4000000)


#use fast_io(B)

//this is the ISR


#INT_RTCC
void tmr0_int(void)
{
output_toggle(PIN_B0);
clear_interrupt(INT_RTCC);
}

void main()
{
set_tris_b(0x00);
output_low(PIN_B0);
setup_timer_0(RTCC_INTERNAL | RTCC_DIV_1);

//TMR0 uses the internal clock,

enable_interrupts(INT_RTCC | GLOBAL);

//no prescaler
//enable TMR0 interrupt and
//enable all interrupts

Fosc/4

while(TRUE);
}

//do nothing and wait for TMR0 interrupt

You might also like