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 PC’s 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 can’t 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 it’s cost /computing power is quite astonishing for its size. A several-million dollar “super-
computer” of the late 1960’s 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 can’t 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. Let’s 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. It’s 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 don’t 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 ). It’s good to start exploring the features of ISIS*7 (the
Pr*oteu*s schematic capture program). (I’m 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 … let’s 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 doesn’t 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, let’s 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:
Don’t 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
programmers’s 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 1’s. 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).

Let’s 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 shouldn’t 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 0’s 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 let’s write the complete Hello, LED program.

Code:

#include <pic.h>

void main()
{
TRISB = 0b00000000; //PORTB are output
PORTB = 0b00000001; //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 don’t 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

Let’s 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 doesn’t know what TRISB and PORTB is.

The next two lines

Code:
TRISB = 0b00000000; //PORTB are output
PORTB = 0b00000001; //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.

Let’s modify the program above and use hexadecimal notation instead of binary notation.

Code:
#include <pic.h>

void main()
{
TRISB = 0x00; //PORTB are output
PORTB = 0x01; //LED on

for(;;); //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; //RB0 is output
PORTB |= 0x01; //LED on

while(1); //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; //RA3 is output
PORTA |= 0x08; //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; //RB0, RB1, RB2, and RB3 are output
PORTB |= 0x0F; //turn on the 4 LEDs

while(1); //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; //RA<3:0> are output


PORTA &= ~0x0F; //turn off the two motors

while(1) //infinite loop


{
//mobot codes goes here



}
}

If you don’t 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 pin is configured as an output
RB0 = 1; //RB0 pin output an approximtely 5V DC signal
//and LED will turn on

while(1); //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 RB0


#define ON 1

void main( )
{
TRISB0 = 0; //RB0 pin is configured as an output
LED = ON; //RB0 pin output an approximtely 5V DC signal
//and LED will turn on

while(1); //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 1

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 doesn’t 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; //RB0 is output
RB1 = 0; //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 is an output pin
RB4 = 1; //BLUE LED is on

TRISA1 = 0; //RA1 is an output pin


RA1 = 0; //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) //this is a user-defined function with a


{ //name init_PORTS
TRISB4 = 0; //RB4 is output
RB4 = 0; //LED1 is off

TRISA1 = 0; //RB1 output


RA1 = 0; //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(); //call/invoke the user-defined function defined above
//and initialize RB4, RB1, and RA1

RB4 = 1; //turn on LED1

while(1) //this is an infinite loop


{
if (RB1==0) //if pushbutton is pressed
RA1 = 1; //LED2 is on
else //if pushbutton is NOT pressed
RA1 = 0; //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 sub-
programs 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 is output
RB0 = 0; //LED is off

while(1)
{
RB0 = 1; //LED is ON
RB0 = 0; //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) //this is a software delay function


{
unsigned int i;

for(i=0;i<0xFFFF;i++) //FOR loop does nothing, just


; //increment variable i from 0
} //to 65535

void main()
{
TRISB &= ~0x01; //initialize RB0 pin as output
PORTB &= ~0x01; //LED is initially off

while(1) //infinite loop


{
PORTB |= 0x01; //LED is on
delay(); //invoke delay() function

PORTB &= ~0x01; //LED is off


delay(); //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) //this is a delay function


{
unsigned int i;

for(i=0;i<0xFFFF;i++) //FOR loop does nothing, just


; //increment variable i from 0
} //to 65535

void main()
{
TRISB &= ~0x01; //initialize RB0 pin as output
PORTB &= ~0x01; //LED is initially off

while(1) //infinite loop


{
PORTB ^= 0x01; //Toggle RB0
delay(); //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) //this is a delay function


{
unsigned int i;

for(i=0;i<0xFFFF;i++) //FOR loop does nothing, just


; //increment variable i from 0
} //to 65535

void main()
{
TRISB &= ~0x05; //RB0 and RB2 are output pins
PORTB &= ~0x01; //LED at RB0 is initially off
PORTB |= 0x04; //LED at RB2 is initially on

while(1) //infinite loop


{
PORTB ^= 0x05; //Toggle RB0 and RB2
delay(); //invoke delay() function
}
}

Here is our next example program.

Let’s 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) //this is a delay function


{
unsigned int i;

for(i=0;i<0x3FFF;i++) //FOR loop does nothing, just


; //increment variable i from 0
} //to 16382

void init_LEDS(void)
{
TRISB &= ~0x0F; //PORTB<3:0> are output pins
PORTB &= ~0x0F; //All 4 LEDS are off
}

void init_PUSHBUTTONS(void)
{
TRISA |= 0x07; //PORTA<2:0> are input pins
}

void main()
{
init_LEDS(); //initialize PORTB
init_PUSHBUTTONS(); //initialize PORTA

while(RA0==1) //loop while START_PB is not pressed


;

RB0 = 1; //turn on LED1


RB1 = 0; //turn off LED2
RB2 = 0; //turn off LED3
RB3 = 1; //turn on LED4

while(1)
{
PORTB ^= 0x0C; //Toggle RB2 and RB3, LED3 and LED4 will
//blink alternately

if (RA1==0) //if LED2_PB is pressed


RB1 = 1; //LED2 is on
else //if LED2_PB is not pressed
RB1 = 0; //LED2 is off

if (RA2==0) //if STOP_PB is pressed,


break; //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) //this is a delay function that accepts a
variable
{ //The user can send value to this function
unsigned int i; //to control the duration of the delay

for(i=0;i<delay_val;i++)
;
}

void main()
{
unsigned int i;

PORTB &= ~0x01; //RB0 is low


TRISB &= ~0x01; //RB0 is output

while(1)
{
PORTB |= 0x01; //LED on
delay(65535); //call delay function, and pass value 65535

PORTB &= ~0x01; //LED off


delay(32767); //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 4MHZ /* Crystal frequency in MHz */
#endif

#define MHZ *1000L /* number of kHz in a MHz */


#define KHZ *1 /* number of kHz in a kHz */

#if 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; //toggle RB0
DelayMs(250); //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 4MHZ /* Crystal frequency in MHz */
#endif

with this code

Code:
#ifndef XTAL_FREQ
#define XTAL_FREQ 1MHZ /* Crystal frequency in MHz */
#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(); //PORTB<3:0> are output

while(1){
PORTB = 0x00; //display 0
delay();
PORTB = 0x01; //display 1
delay();
PORTB = 0x02; //display 2
delay();
PORTB = 0x03; //display 3
delay();
PORTB = 0x04; //display 4
delay();
PORTB = 0x05; //display 5
delay();
PORTB = 0x06; //display 6
delay();
PORTB = 0x07; //display 7
delay();
PORTB = 0x08; //display 8
delay();
PORTB = 0x09; //display 9
delay();
PORTB = 0x0A; //display 10
delay();
PORTB = 0x0B; //display 11
delay();
PORTB = 0x0C; //display 12
delay();
PORTB = 0x0D; //display 13
delay();
PORTB = 0x0E; //display 14
delay();
PORTB = 0x0F; //display 15
delay();
}
}

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(); //PORTB<3:0> are output

while(1){
PORTB = 0x01; //display value 0000 0001
delay();
PORTB = 0x02; //display value 0000 0010
delay();
PORTB = 0x04; //display value 0000 0100
delay();
PORTB = 0x08; //display value 0000 1000
delay();
PORTB = 0x10; //display value 0001 0000
delay();
PORTB = 0x20; //display value 0010 0000
delay();
PORTB = 0x40; //display value 0100 0000
delay();
PORTB = 0x80; //display value 1000 0000
delay();
PORTB = 0x40; //display value 0100 0000
delay();
PORTB = 0x20; //display value 0010 0000
delay();
PORTB = 0x10; //display value 0001 0000
delay();
PORTB = 0x08; //display value 0000 1000
delay();
PORTB = 0x04; //display value 0000 0100
delay();
PORTB = 0x02; //display value 0000 0010
delay();
PORTB = 0x01; //display value 0000 0001
delay();

}
}

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 NOWDT //No Watch Dog Timer


#FUSES XT //Crystal osc <= 4mhz
#FUSES NOPUT //No Power Up Timer
#FUSES NOPROTECT //Code not protected from reading

#use delay(clock=4000000)

void main()
{
set_tris_b(0x00); //PORTB are output
output_b(0x00); //all LEDs are off

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);
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 0x00


#define SHIFT_LEFT 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; //display to PORTB
value = value << 1; //shift left
if (value == 0x80) //check if next value to be
displayed is 0x80
direction = SHIFT_RIGHT; //reverse direction
delay();

//RB7 on --> RB6 --> .... RB0


if (direction == SHIFT_RIGHT)
{
PORTB = value; //display to PORTB
value = value >> 1; //shift right
if (value == 0x01) //check if next value to be
displayed is 0x01
direction = SHIFT_LEFT; //reverse direction
delay();
}
}
}

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(); //PORTB<6:0> are output pins

while(1)
{
PORTB = ~0x3F; //0
delay();
PORTB = ~0x06; //1
delay();
PORTB = ~0x5B; //2
delay();
PORTB = ~0x4F; //3
delay();
PORTB = ~0x66; //4
delay();
PORTB = ~0x6D; //5
delay();
PORTB = ~0x7D; //6
delay();
PORTB = ~0x07; //7
delay();
PORTB = ~0xFF; //8
delay();
PORTB = ~0x6F; //9
delay();
}
}

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(); //PORTB<6:0> are output pins

while(1)
{
switch(value)
{
case 0x00:
PORTB = ~0x3F; //0
break;
case 0x01:
PORTB = ~0x06; //1
break;
case 0x02:
PORTB = ~0x5B; //2
break;
case 0x03:
PORTB = ~0x4F; //3
break;
case 4:
PORTB = ~0x66; //4
break;
case 5:
PORTB = ~0x6D; //5
break;
case 6:
PORTB = ~0x7D; //6
break;
case 7:
PORTB = ~0x07; //7
break;
case 8:
PORTB = ~0xFF; //8
break;
case 9:
PORTB = ~0x6F; //9
break;
}

value++; //increment value

if (value > 0x09) //if value is more than 9,


value = 0x00; //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; //send value to 7-segment driver
value++; //increment value

if (value > 0x09) //if value is more than 9,


value = 0x00; //reset value back to 0

delay(); //0.5 seconds delay


}
}

and the simulation


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 &= ~0x0F; //PORTB<3:0> are output


TRISB &= ~0x0F;
PORTB &= ~0x03; //PORTA<1:0> are output
TRISA &= ~0x03;
while(1)
{
PORTA = 0x01; //enable SEGMENT1, disable SEGMENT2
PORTB = 0x01; //display '1'
delay(); //500ms delay

PORTA = 0x02; //enable SEGMENT2, disable SEGMENT1


PORTB = 0x04; //display '4'
delay(); //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; //number to be displayed
unsigned char tens_digit; //holds the tens digit of display_value
unsigned char ones_digit; //holds the ones digit of display_value

PORTB &= ~0x0F; //PORTB<3:0> are output pins


TRISB &= ~0x0F;

PORTA &= ~0x03; //PORTA<1:0> are output pins


TRISA &= ~0x03;

tens_digit = display_value/10; //get the tens digits, = 4


ones_digit = display_value - (tens_digit * 10); //get the ones digit, = 1
while(1)
{
ENABLE_SEGMENT1;
PORTB = ones_digit; //display '1'
delay(); //500ms delay

ENABLE_SEGMENT2;
PORTB = tens_digit; //display '4'
delay(); //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; //get the tens digits, = 4
ones_digit = display_value - (tens_digit * 10); //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 (PORTA=0x01)
#define ENABLE_SEGMENT2 (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 (PORTA=0x01)
#define ENABLE_SEGMENT2 (PORTA=0x02)

//function prototype
void delay(void);

void main()
{
unsigned char display_value = 0x00; //stores any value between 0-99, value to be
displayed to 7-segment
unsigned char tens_digit; //stores the tens digit of display_value
unsigned char ones_digit; //stores the ones digit of display_value

PORTB &= ~0x0F; //PORTB<3:0> are output


TRISB &= ~0x0F;

PORTA &= ~0x03; //PORTA<1:0> are output


TRISA &= ~0x03;

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

ENABLE_SEGMENT1; //enable SEGMENT1


PORTB = ones_digit; //display ones digit
delay(); //250ms delay

ENABLE_SEGMENT2; //enable SEGMENT2


PORTB = tens_digit; //display tens digit

display_value++; //increment number, 0-99

if (display_value > 99) //if more than 99


display_value = 0x00; //reset back to 0

delay(); //250ms delay


}
}

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

Here is the simulation.


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; //stores any value between 0-99, value to be
displayed to 7-segment
unsigned char tens_digit; //stores the tens digit of display_value
unsigned char ones_digit; //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; //PORTB<3:0> are output
TRISB &= ~0x0F;

PORTB &= ~0x03; //PORTA<1:0> are output


TRISA &= ~0x03;

update_value(display_value); //initial update

while(1)
{
display(); //display display_value to 7-
segments

temp++;

if (temp == 9) //when temp==9 ,approx. 1 second has


elapsed
{
display_value++; //increment value

if (display_value > 99) //if more than 99


display_value = 0x00; //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; //enable SEGMENT1
PORTB = ones_digit; //display '1'
DelayMs(50); //50ms delay

PORTA = 0x02; //enable SEGMENT2


PORTB = tens_digit; //display '4'
DelayMs(50); //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 end of string is not reached
{
while(!TXIF)
; //wait while previous char is being transmitted
TXREG = *s; //send character to UART transmitter register, char
send automatically
s++; //point to next character
}
}

sample use:

Code:
void main()
{
//initialization codes here
init_uart();

while(1)
{
puts("sevenstring impakta\r"); //send string every 1/4 seconds
DelayMs(250);
}
}

same function as above

Code:
void puts(const unsigned char *s)
{
while(*s++) //while end of string is not reached
{
while(!TXIF)
; //wait while previous char is being transmitted
TXREG = *s; //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; //read Receive buffer


if (c == 'a' || c == 'A' ) //if 'a' or 'A'
RB0 ^= 1; //toggle LED

if(OERR) //clear error if there is any


{
CREN = 0;
CREN = 1;
}
}
}

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, isn’t 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; //RB0 pin is output
PORTB &= ~0x01; //LED is off
//initialize TMR0
INTCON &= ~0x80; //disable all interrupts (GIE = 0)
OPTION &= ~0x20; //TMR0 uses the internal clock, Fosc/4 (T0CS = 0)
OPTION |= 0x08; //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; //clear any previous/pending TMR0 interrupt (T0IF = 0)


INTCON |= 0x20; //enable TMR0 interrupt (T0IE = 1)
INTCON |= 0x80; //enable all interrupts (GIE = 1)

while(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; //toggle LED
INTCON &= ~0x04; //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; //RB0 pin is output
PORTB &= ~0x01; //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; //disable all interrupts (GIE = 0)
OPTION &= ~0x20; //TMR0 uses the internal clock, Fosc/4 (T0CS = 0)
OPTION |= 0x08; //prescaler is assigned to the Watch Dog Timer (PSA
= 1)
//The WDT is disable, however.
TMR0 = 0x00; //TMR0 starts at zero.
INTCON &= ~0x04; //clear any previous/pending TMR0 interrupt (T0IF =
0)
INTCON |= 0x20; //enable TMR0 interrupt (T0IE = 1)
INTCON |= 0x80; //enable all interrupts (GIE = 1)

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; //toggle LED


//or
//RB0 ^= 1;
T0IF = 0; //clear TMR0 interrupt flag
}

void main()
{
//initialize RB0

TRISB &= ~0x01; //RB0 pin is output


RB0 = 0; //LED is off

//initialize TMR0
GIE = 0; //disable all interrupts
T0CS = 0; //TMR0 uses the internal clock, Fosc/4
PSA = 1; //prescaler is assigned to WDT

T0IF = 0; //clear any previous/pending TMR0 interrupt


T0IE = 1; //enable TMR0 interrupt
GIE = 1; //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; //toggle LED


//or
//RB0 ^= 1;
T0IF = 0; //clear TMR0 interrupt flag
}

void init_TMR0(void)
{
GIE = 0; //disable all interrupts
T0CS = 0; //TMR0 uses the internal clock, Fosc/4
PSA = 1; //prescaler is assigned to WDT

T0IF = 0; //clear any previous/pending TMR0 interrupt


T0IE = 1; //enable TMR0 interrupt
GIE = 1; //enable all interrupts
}

void main()
{
//initialize RB0
TRISB &= ~0x01; //RB0 pin is output
RB0 = 0; //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,


Fosc/4
//no prescaler
enable_interrupts(INT_RTCC | GLOBAL); //enable TMR0 interrupt and
//enable all interrupts

while(TRUE); //do nothing and wait for TMR0 interrupt


}

You might also like