PIC Tutorial Paranz
PIC Tutorial Paranz
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.
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.
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.
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 ).
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.
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.
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.
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).
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.
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)
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
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
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.
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
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;
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.
Code:
TRISB &= 0b11111110;
PORTB |= 0b00000001;
or
Code:
TRISB &= ~0x01;
PORTB |= 0x01;
Code:
#include <pic.h>
void main()
{
TRISB &= ~0x01; //RB0 is output
PORTB |= 0x01; //LED on
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
}
Code:
#include <pic.h>
void main()
{
TRISB &= ~0x0F; //RB0, RB1, RB2, and RB3 are output
PORTB |= 0x0F; //turn on the 4 LEDs
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
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
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>
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
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.
Code:
#define ON 1
Code:
LED = ON;
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.
The LED will turn on when the output pin is logic 0, off if logic high...
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.
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
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 main()
{
init_PORTS(); //call/invoke the user-defined function defined above
//and initialize RB4, RB1, and RA1
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.
Code:
#include <pic.h>
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:
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 main()
{
TRISB &= ~0x01; //initialize RB0 pin as output
PORTB &= ~0x01; //LED is initially off
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.
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
Code:
#include <pic.h>
//configuration fuses
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void main()
{
TRISB &= ~0x01; //initialize RB0 pin as output
PORTB &= ~0x01; //LED is initially off
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 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
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.
Code:
#include <pic.h>
//configuration fuses
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
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(1)
{
PORTB ^= 0x0C; //Toggle RB2 and RB3, LED3 and LED4 will
//blink alternately
When the STOP_PB is pressed, you may press the RESET button to reset the MCU program execution.
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;
while(1)
{
PORTB |= 0x01; //LED on
delay(65535); //call delay function, and pass value 65535
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.
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
delay.c
Code:
#include "delay.h"
void
DelayMs(unsigned char cnt)
{
#if XTAL_FREQ <= 2MHZ
do {
DelayUs(996);
} 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.
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.
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
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.
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;
}
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:
#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;
}
void main()
{
unsigned char value = 0x00;
while(1){
}
}
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
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;
}
void main()
{
unsigned char value = 0x00;
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
Code:
#include <16F84A.h>
#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);
//initialize PORTB
void init_LED(void)
{
PORTB &= ~0xFF;
TRISB &= ~0xFF;
}
void main()
{
unsigned char value = 0x01;
unsigned char direction;
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();
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
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).
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.
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();
}
}
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
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.
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;
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;
}
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;
while(1)
{
PORTB = value; //send value to 7-segment driver
value++; //increment value
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.
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()
{
}
}
void delay(void)
{
DelayMs(250);
DelayMs(250);
}
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
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()
{
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
ENABLE_SEGMENT2;
PORTB = tens_digit; //display '4'
delay(); //500ms delay
}
}
void delay(void)
{
DelayMs(250);
DelayMs(250);
}
Code:
tens_digit = display_value/10; //get the tens digits, = 4
ones_digit = display_value - (tens_digit * 10); //get the ones digit, = 1
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)
Code:
//PORTB<3:0> to 74LS47
//RA0 enable/disable SEGMENT1
//RA1 enable/disable SEGMENT2
#include <pic.h>
#include "delay.c"
//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
while(1)
{
tens_digit = display_value/10; //tens digit
ones_digit = display_value - (tens_digit * 10); //ones digit
void delay(void)
{
DelayMs(250);
}
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"
//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;
while(1)
{
display(); //display display_value to 7-
segments
temp++;
}
}
//display to 7segment
void display(void)
{
PORTA = 0x01; //enable SEGMENT1
PORTB = ones_digit; //display '1'
DelayMs(50); //50ms delay
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.
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))
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);
}
}
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>
if (RCIF)
{
Pa'no kung "string" ang inaabangan instead of single "character" lang gaya nito?
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;
}
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?
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 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
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>
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
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.
Code:
//initialize RB0
TRISB &= ~0x01; //RB0 pin is output
PORTB &= ~0x01; //LED is off
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.
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)
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.
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>
void main()
{
//initialize RB0
//initialize TMR0
GIE = 0; //disable all interrupts
T0CS = 0; //TMR0 uses the internal clock, Fosc/4
PSA = 1; //prescaler is assigned to WDT
Code:
#include <pic.h>
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
void main()
{
//initialize RB0
TRISB &= ~0x01; //RB0 pin is output
RB0 = 0; //LED is off
//initialize TMR0
init_TMR0();
Code:
#include <16f84a.h>
#fuses XT, NOWDT, NOPUT, NOPROTECT //configuration fuses
#use delay (clock = 4000000)
#use fast_io(B)
void main()
{
set_tris_b(0x00);
output_low(PIN_B0);