pic tutorial paranz

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

Upload: el-pandesal-domingo

Post on 24-Mar-2015

419 views

Category:

Documents


7 download

TRANSCRIPT

Page 1: PIC Tutorial Paranz

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

Page 2: PIC Tutorial Paranz

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.

Page 3: PIC Tutorial Paranz

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.

Page 4: PIC Tutorial Paranz

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

Page 5: PIC Tutorial Paranz

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 )

Page 6: PIC Tutorial Paranz

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.

Page 7: PIC Tutorial Paranz

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.

Page 8: PIC Tutorial Paranz

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

Page 9: PIC Tutorial Paranz

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.

Page 10: PIC Tutorial Paranz

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

Page 11: PIC Tutorial Paranz

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

Page 12: PIC Tutorial Paranz

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

Page 13: PIC Tutorial Paranz

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).

Page 14: PIC Tutorial Paranz

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.

Page 15: PIC Tutorial Paranz

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.

Page 16: PIC Tutorial Paranz

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

Page 17: PIC Tutorial Paranz

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.

Page 18: PIC Tutorial Paranz

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

Page 19: PIC Tutorial Paranz

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

Page 20: PIC Tutorial Paranz

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

Page 21: PIC Tutorial Paranz

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

Page 22: PIC Tutorial Paranz

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

Page 23: PIC Tutorial Paranz

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

}

}

Page 24: PIC Tutorial Paranz

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

}

}

Page 25: PIC Tutorial Paranz

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

}

}

Page 26: PIC Tutorial Paranz

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

}

}

Page 27: PIC Tutorial Paranz

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.

Page 28: PIC Tutorial Paranz

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)

Page 29: PIC Tutorial Paranz

{

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

Page 30: PIC Tutorial Paranz

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; }

Page 31: PIC Tutorial Paranz

#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;

Page 32: PIC Tutorial Paranz

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:

Page 33: PIC Tutorial Paranz

#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)

Page 34: PIC Tutorial Paranz

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();

Page 35: PIC Tutorial Paranz

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

Page 36: PIC Tutorial Paranz

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

Page 37: PIC Tutorial Paranz

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();

Page 38: PIC Tutorial Paranz

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

Page 39: PIC Tutorial Paranz

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

Page 40: PIC Tutorial Paranz

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:

Page 41: PIC Tutorial Paranz

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

Page 42: PIC Tutorial Paranz

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();

Page 43: PIC Tutorial Paranz

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).

Page 44: PIC Tutorial Paranz

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)

{

Page 45: PIC Tutorial Paranz

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

Page 46: PIC Tutorial Paranz

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

Page 47: PIC Tutorial Paranz

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.

Page 48: PIC Tutorial Paranz

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;

Page 49: PIC Tutorial Paranz

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.

Page 50: PIC Tutorial Paranz

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

Page 51: PIC Tutorial Paranz

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:

Page 52: PIC Tutorial Paranz

//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.

Page 53: PIC Tutorial Paranz

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;

Page 54: PIC Tutorial Paranz

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

Page 55: PIC Tutorial Paranz

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

}

}

Page 56: PIC Tutorial Paranz

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.

Page 57: PIC Tutorial Paranz

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

Page 58: PIC Tutorial Paranz

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,

Page 59: PIC Tutorial Paranz

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.

Page 60: PIC Tutorial Paranz

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

Page 61: PIC Tutorial Paranz

//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.

Page 62: PIC Tutorial Paranz

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

Page 63: PIC Tutorial Paranz

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:

Page 64: PIC Tutorial Paranz

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

Page 65: PIC Tutorial Paranz

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

Page 66: PIC Tutorial Paranz

#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

}