Professional Documents
Culture Documents
PIC Tutorial Paranz
PIC Tutorial Paranz
Table 1. A comparison
Based on the table above, you may conclude correctly that the PIC16F84A MCU pales in comparison with the
desktop PC. The MCUs cant perform word processing nor can you play your favorite PC games with it.
Nevertheless, MCUs has sufficient computing capability to control, say, a DC motor, display text to an LCD,
turn on a relay, scan a keypad, control a small robot, display numerical values to a 7-segment display (like in
Figure 2 below), send a serial data to PC, etc. Its tiny form factor makes it easy to embedded into many
applications. And its cost /computing power is quite astonishing for its size. A several-million dollar supercomputer of the late 1960s weighs several tones, fit into a very large room (a hall, actually
), and is 1000
times slower than this MCU.
Let's continue...
Figure 4 below shows a typical schematic for PIC16F84a circuit. The PIC MCU is a +5V-powered device (5V
supply connected to VDD pin). The device datasheet indicates a VDD(MIN) = +4V and VDD(MAX) = +5.5 V, so we
are on the safe side if we use 5V. Its also easier to generate +5V using the widely available LM7805 voltage
regulators.
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.
).
In the circuit above, /MCLR pin is always pulled up to +5V. This may be a disadvantage if PIC MCu locks up
and stop executing the program due to power glitches or excessive electrical noise (for example, a large motor
nearby is generating large electrical fields and affect the MCU circuitry). Without SW1 in the previous circuit,
the user will have no means to reset the device other than to disconnect/reconnect the +5V power supply.
The oscillator circuit also uses an RC circuit (R2 and C1 above). This is just one of the five ways of clocking a
PIC (the first one is already shown in the Figure 4/5 using a crystal oscillator). It is also cheap (<P1.00) and is
an acceptable clock source for low cost, timing-insensitive applications. RC-based oscillator circuits are
generally limited to clock frequencies less than 1 Mhz and its main disadvantage is its inaccuracy.
As recommended in the datasheet, you may use R values between 5k and 100k and C values equal or greater
than 20 pf. As a start, try using 10kohm/20 pf RC values. You may get clock frequency ranging from 600 kHz
to 650 kHz, but dont freak out if you are 20% above or below that range. Again, its not accurate!
Now, let us improve the circuit in Figure 4 by connecting an LED to the RB0 pin. In our very first example
program, we will configure RB0 as an output pin, and then command the MCU to output a logic 1 at this pin. If
an output pin (like RB0) has logic 1, it means that a 5V signal appears at this pin with respect to circuit ground.
Simply put, our program will turn on the LED connected at RB0, since the 5V will forward bias the LED. This
is probably the simplest program that we can start with.
You may now build the circuit in Pr*oteu*s (Figure 8 ). Its good to start exploring the features of ISIS*7 (the
Pr*oteu*s schematic capture program). (Im not gonna make a Proteus tutorial anytime soon, so you have to
learn it yourself )
NOTE1: In Microchip literature, the internal registers are classified into two: (1) the special function registers
(SFR) and the (2) general function registers (GFR). The GFR (or GPR, general purpose registers) are the
main memory of the MCU. They are simply called the RAM.
The user doesnt have to know all of these SFRs to be able to use the MCU, but applying the 10% principle on
Getting Started means we have to understand maybe 2 or 3 of these special function registers at the start. And
that is what we are going to do.
The user can also think of the MCU as being controlled by the program stored in the MCU. The program in
turn, access and modify the SFRs content, as well as do some other things.
Furthermore, you can also view these special function registers as tiny switches that the program can turn on
and turn off. (Physically, a register is made up of flip-flops which can hold binary logic values in the form of
stored electric charges. But the physical make-up of the registers is not important for us MCU users. The
register/switch analogy should be sufficient for understanding).
Since the internal registers are normally 8 bits wide, then a register can be thought of as a group of 8 switches,
1 switch corresponds to a bit. These tiny switches can be turn on/turn off by writing a 1 or 0, respectively.
The RB0 pin (as well as the rest of the PORTB pins; remember that PORTB = RB<7:0>) is controlled by two
SFRs, namely, TRISB and PORTB (indicated in Table 3 below). TRISB is called the PORTB Data
Direction register. The binary value stored in the bit locations of TRISB will determine whether a PORTB pin
is an input or an output pin. If a TRISB bit is 0, then the corresponding RBx pin is an output pin; if 1, then it is
an input pin.
For example, since we want RB0 to be an output pin, then TRISB<0> (it means TRISB bit 0) must be 0. If we
want RB6 to be an input pin, then TRISB<6> must be 1.
As another example, lets say that TRISB register holds a binary value of 11110000 (Table 4). This means that
RB<3:0> pins are output pins while RB<7:4> pins are input pins. (Figure 9)
Remember that at any one time, a GPIO pin is either an input or an output pin. An output pin can send an
output signal (either 5V or 0V), and an input pin can read an external signal (5V or 0V).
The PORTB register is the Data Latch register for PORTB/RB<7:0> (NOTE1). The value stored in PORTB register will be
latched out (i.e. sent) to the physical RBx pin(s) if the pin(s) is configured as an output pin. If the PORTB pin is an input
pin, the external logic signal appearing at the pin will be latched into the PORTB register. For example, if RB0 is
configured as an output pin (via TRISB), and a logic 1 is written/stored into PORTB<0>, then a +5V signal will appear at
the RB0 pin (NOTE2). If logic 0 is written into PORTB<0>, a 0V will appear instead.
Also, if for example RB7 is configured as an input pin, then an external +5V signal appearing at the pin (from an external
unknown circuit) will cause a 1 to be written into PORTB<7>. If the external signal is 0V, a 0 is written instead.
NOTE1:
Dont be confused with PORTB, the internal register, with PORTB, the collective name of the 8 external pins (that is,
RB<7:0>) you see dangling at the side of the chip. If you are confused, well, that is still ok because they are basically
synonymous (PORTB<0>, the least significant bit is the basically same as RB0 (pin6) in the discussion), according to the
programmerss perspective. But for the hardware purist (or the clueless
), they are actually different. The first PORTB
is a flip-flop (a register) inside the MCU, while the 2nd PORTB are the 8 visible pin connector.
The 8 pins have names, RB0..... RB7, which happens to be the name of the individual bits in the PORTB register.
Confused, still?
NOTE2:
The +5V signal is coming from the MCU itself. It is safe to think that there is an imaginary +5V battery source inside RB0.
When PORTB<0> is set (i.e. 1 is written), an imaginary switch is closed and connects the positive terminal of the
imaginary battery to the external RB0 pin. The imaginary switch can then be controlled via software and you can do
many things with this capability, like turn on/off an LED, switch a relay, start/stop a motor, send a signal to an LCD, hack
a PC, or nuke a city somewhere in Africa, etc.
The same discussion above applies to RA<4:0> pins, but using PORTA and TRISA registers instead. PORTA only have 5
pins for the PIC16F84A chip, and are mapped to the lower 5 bits of the PORTA and TRISA SFRs.
In addition, when the PIC16F84a device is first powered up or when it is reset, all the GPIO pins are configured as input
pins. TRISA and TRISB are all 1s. You may verify this in Table 2, 2nd column from the right.
By now, you would have already guessed how to turn on an LED connected at RB0. To do this, TRISB<0> must be cleared
(i.e. written with 0) and PORTB<0> must be set (i.e. written with 1).
Lets make our very first Hello, LED program. This is what we need to do; make TRISB<0> = 0 and
PORTB<0> = 1, and voil! the precious LED lights up.
In Hi-Tech C, the line
Code:
TRISB = 0b00000000;
will configure RB0 as an output pin. Since the rest of the upper 7 bits are also 0, the rest of PORTB pins are
also configured as output pins. This shouldnt bother us much since nothing is connected to the RB<7:1> pins.
The prefix 0b will tell the compiler to interpret the next 8 0s as representing a binary number (the decimal 0).
Next, the line
Code:
PORTB = 0b00000001;
will command the PIC to output a +5V signal at RB0 pin, since PORTB<0> is 1. This will turn on the LED.
while(1);
//infinite loop
Make a new project in MPLAB. You can name the project any name you want. Hello_LED is a good name.
Type the sample code above and dont forget to set the correct CONFIGURATION bits in the Configure
menu (XT, OFF, OFF, OFF). Compile/build the project.
In the project directory, there are several output files generated during the compile/build process. The most
important output files are the hello_led.coff and hello_led.hex.
The next step is to include the hello_led.hex (or hello_led.coff) into the simulated circuit in Proteus.
In the PIC circuit in ISIS (shown in Figure 8 above), double click the PIC16F84a device on the edit window.
This will open the Edit Component window. (Figure 10)
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).
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.
If you leave out the #include <pic.h> line, the compiler will output an error message
Quote
undefined identifier: TRISB
undefined identifier: PORTB
will configure all PORTB pins as output and output a logic high at RB0 pin. A +5V will appear at RB0. Since
an LED is connected at RB0, it will light up.
The last line is an infinite loop.
Code:
while(1);
This empty while() loop will keep on iterating (but it executes nothing) since the condition inside the
parenthesis will always be true (i.e. 1 = TRUE, 0 = FALSE). It is included to keep the main program from
exiting. Without the infinite loop, the MCU will execute the program, then exit, then re-execute again the
program (since the address pointer will overflow back to the start of the program), execute, exit again and so on.
Lets modify the program above and use hexadecimal notation instead of binary notation.
Code:
#include <pic.h>
void main()
{
TRISB = 0x00;
PORTB = 0x01;
for(;;);
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;
The & is the symbol for bitwise AND operation. TRISB<0> is now 0 while TRISB<7:1> bits remain
unmodified.
As an example for (2) above, assume PORTB<0> must be set (i.e. written with 1) like in the Hello, LED sample
program. The code should be
Code:
PORTB = PORTB | 0b00000001;
The | is the symbol for bitwise OR operation. PORTB<0> is now 1 while PORTB<7:1> bits remain unchanged.
In shorthand C notation, the two line above are normally written as
Code:
TRISB &= 0b11111110;
PORTB |= 0b00000001;
or
Code:
TRISB &= ~0x01;
PORTB |= 0x01;
//RB0 is output
//LED on
//infinite loop
Another example, if you want to use RA3 instead of RB0, here is the code.
Code:
#include <pic.h>
void main()
{
TRISA &= ~0x08;
PORTA |= 0x08;
//RA3 is output
//LED on
while(1);
//infinite loop
If 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;
while(1)
//infinite loop
{
//mobot codes goes here
}
}
If you dont like the previous codes above for modifying bits in registers, you can always use the name of the
individual bits of registers instead.
Code:
#include <pic.h>
void main( )
{
TRISB0 = 0;
RB0 = 1;
while(1);
TRISB0 is the name of TRISB<0> bit and RB0 is the name of PORTB<0> bit.
RB0
1
void main( )
{
TRISB0 = 0;
LED = ON;
while(1);
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
The LED will turn on when the output pin is logic 0, off if logic high...
//RB0 is output
//Turn on LED
while(1);
}
I added a __CONFIG() statement before main() since i'm quite lazy in setting the config. bits in the
Configuration menu
Here is our next example, consisting of 2 LEDs and 1 pushbutton. The program will turn on an LED connected
at RB4 while another LED at RA1 will turn on only if the pushbutton connected at RB1 is pressed.
TRISA1 = 0;
RA1 = 0;
TRISB1 = 1;
while(1)
{
if(RB1==0)
//if pushbutton is pressed, turn on RED LED
{
RA1 = 1;
}
else
//else, RED LED is OFF
RA1 = 0;
}
}
RB4 and RA1 are configured as output pins, while RB1 is an input pin. When the pushbutton is pressed, 0V
will appear at RB1 so that it will hold a logic 0. If the pushbutton is not pressed, +5V will appear since RB1 pin
is pulled up to +5V via R4 (10kohms).
Here is another code similar to our previous example. This code has a user-defined C Function.
Code:
#include <pic.h>
//configuration bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void init_PORTS(void)
{
TRISB4 = 0;
RB4 = 0;
TRISA1 = 0;
RA1 = 0;
//RB1 output
//LED2 is off
TRISB1 = 1;
//RB1 is input
RB4 = 1;
while(1)
//this
{
if (RB1==0)
RA1 = 1;
else
RA1 = 0;
}
}
is an infinite loop
//if pushbutton is pressed
//LED2 is on
//if pushbutton is NOT pressed
//LED2 is off
A C function is simply a self-contained block of C code. A function has a name, and you can call/invoke a function using
its name. When a function is called/invoked, the C code inside this function (that is, the function body!) is executed.
Why do we need to use functions?
1. Functions can be invoked anywhere in the program. Which means that a particular code (the code inside the
function) can be used again and again, resulting to a much smaller program size. Without functions, programs can grow
very, very large since you would need to replicate the same code again and again at several points in the program.
2. Using functions allow us to easily break a large program into manageable chunks. Thinks of functions as subprograms inside your C program.
In the above program, there is a function named init_PORTS(). The parenthesis after the function name is included to
denote that init_PORTS is a C function.
In the above program, the function is called (or invoked) at the start of main(). The function body (that is, the block of
code inside the function declaration) is executed when the function is called.
).
//RB0 is output
//LED is off
//LED is ON
//LED is OFF
Using software-based delay (or software delay, for short), the idea is simply to make the CPU execute portion
of codes that actually does nothing other than to "waste" CPU execution times. The simplest way it to use an
empty For loop.
Code:
for(i=0;i<=0xFFFF;i++)
;
//empty body
In the above code, the FOR loop has an empty body (no statements). The FOR loop will simply cause the CPU
to count from 0 to 65535 (0xFFFF), then it exits.
Let's improve the previous code. I will put the FOR loop delay inside a function, named delay().
Then i will simply call the delay() function with a delay(); statement anywhere in main() .
Code:
#include <pic.h>
//configuration fuses
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void delay(void)
{
unsigned int i;
for(i=0;i<0xFFFF;i++)
;
void main()
{
TRISB &= ~0x01;
PORTB &= ~0x01;
while(1)
{
PORTB |= 0x01;
delay();
PORTB &= ~0x01;
delay();
}
}
//infinite loop
//LED is on
//invoke delay() function
//LED is off
//invoke delay() function
This "blinker" program is a better program than the previous one, in terms of memory usage since there is only
1 FOR loop (inside the delay() function) that can be executed many times over. The previous "blinker" program
has 2 FOR loops inside the main() function.
You may check the total program memory space used during compilation. This "blinker" code consumed only
28 words (the smaller program memory used, the better), while the previous code has 33 words.
This sample program demonstrates the advantages of using C functions.
Let's improve the "blinker" program once again. In turning on/off the LED, we will use the ^ operator (XOR
bit-wise operator).
As a review, when a bit is XORed with 1, this bit will be toggled. For example, if this bit is 0, XORing it with 1
will changed its value to 1. If the the bit is 1, XORing will changed the value to 0
In summary,
0^1=1
1^1=0
In the above program, RB0 bit (PORTB<0>) is toggled again and again. Since an LED is connected at RB0,
this LED will "blink"
When the PIC MCU is powered up, it will not "respond" until START_PB is pressed. LED1 (at RB0) will turn
on when START_PB is pressed, and LED3 and LED4 (at RB2 and RB3, respectively) will start blinking
alternate to each other. If LED2_PB is pressed, LED2 (at RB2) will turn on.
If STOP_PB is pressed, the PIC MCU stops responding to the pushbuttons and all the LEDs are off.
Here is the code:
Code:
#include <pic.h>
//configuration fuses
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void delay(void)
{
unsigned int i;
for(i=0;i<0x3FFF;i++)
;
}
void init_LEDS(void)
{
TRISB &= ~0x0F;
PORTB &= ~0x0F;
}
void init_PUSHBUTTONS(void)
{
TRISA |= 0x07;
}
void main()
{
init_LEDS();
init_PUSHBUTTONS();
//initialize PORTB
//initialize PORTA
while(RA0==1)
;
RB0
RB1
RB2
RB3
//turn
//turn
//turn
//turn
=
=
=
=
1;
0;
0;
1;
while(1)
{
PORTB ^= 0x0C;
on LED1
off LED2
off LED3
on LED4
if (RA1==0)
RB1 = 1;
else
RB1 = 0;
if (RA2==0)
break;
delay();
}
PORTB &= ~0x0F;
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.
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);
for(i=0;i<delay_val;i++)
;
}
void main()
{
unsigned int i;
PORTB &= ~0x01;
TRISB &= ~0x01;
//RB0 is low
//RB0 is output
while(1)
{
PORTB |= 0x01;
delay(65535);
//LED on
//call delay function, and pass value 65535
//LED off
//call delay function, and pass value 32767 (=
65535/2)
}
}
Compile and simulate this code. You will notice that the LED is on twice longer than when it is off.
The delay() function above accepts a value/argument when it is called. Try comparing the delay() code in the
previous "blinker" and the code in this "blinker" program.
Let's use a more "advanced" delay() function, but this time, using the sample code from Hi-Tech.
Browse the Hi-Tech C installation directory and go to ...\HT-PIC\samples\delay directory
There are 2 files, delay.c and delay.h, each with the following code:
delay.h
Code:
#ifndef XTAL_FREQ
#define XTAL_FREQ
#endif
#define MHZ
#define KHZ
#if
4MHZ
*1000L
*1
#define DelayUs(x)
#else
#define DelayUs(x)
#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
#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;
//toggle RB0
//delay for 250 ms
Copy and paste the delay.c and delay.h file from the sample folder to the current project directory. In the
blinker project directory, you will now have 3 files:
main.c
delay.c
delay.h
Build the project, and simulate the blinker program in Proteus. The LED at RB0 will turn on/off repeatedly
every 0.5 seconds (250 ms off and 250 ms on).
The main.c program above is quite simple, though there are two lines of code that need a little explanation.
This line
Code:
#include "delay.c"
will include the "delay.c" file ( as well as the delay.h files, since the delay.c "includes" the delay.h) into the
main.c file. We need to add this file since we are going to use the function DelayMs() declared inside this file.
During the compilation process, the compiler will try locate the delay.c and delay.h file in the same directory as
the main.c. If the two files are not found, there will be a compilation error.
inside the while(1) loop will invoke the DelayMs() function that is inside the delay.c file, and pass to it a value
of 250. The DelayMs() will then execute a delay for 250ms.
Conclusion on the above example.
The DelayMs() (as well as the DelayUs() ) function is accurate in creating a delay, compared to our previous
delay() functions. I suggest using this sample code for timing-sensitive programs that uses software delay().
The example above also assumes that the PIC mcu is using a 4 Mhz crystal. If you will use a different crystal
oscillator, you will need to modify the delay.c file.
For example, if you want to use 1 Mhz, open the delay.h file and replace the following code
Code:
#ifndef XTAL_FREQ
#define XTAL_FREQ
#endif
4MHZ
1MHZ
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:
//CONFIG bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
//initialize PORTB
void init_LED(void)
{
PORTB &= ~0x0F;
TRISB &= ~0x0F;
}
//1 second delay
void delay(void)
{
DelayMs(250);
DelayMs(250);
DelayMs(250);
DelayMs(250);
}
void main()
{
init_LED();
while(1){
PORTB = 0x00;
delay();
PORTB = 0x01;
delay();
PORTB = 0x02;
delay();
PORTB = 0x03;
delay();
PORTB = 0x04;
delay();
PORTB = 0x05;
delay();
PORTB = 0x06;
delay();
PORTB = 0x07;
delay();
PORTB = 0x08;
delay();
PORTB = 0x09;
delay();
PORTB = 0x0A;
delay();
PORTB = 0x0B;
delay();
PORTB = 0x0C;
delay();
PORTB = 0x0D;
delay();
//display 0
//display 1
//display 2
//display 3
//display 4
//display 5
//display 6
//display 7
//display 8
//display 9
//display 10
//display 11
//display 12
//display 13
PORTB = 0x0E;
delay();
PORTB = 0x0F;
delay();
//display 14
//display 15
}
}
In the program, we use the software delay function defined in "delay.c" file from the Hi-tech C sample folder.
Make sure that you copy the delay.c and delay.h file into the project folder before building the program.
Let's improve the previous sample program. We will replace the "hard-coded" 0-15 values, but instead use a
variable. This will result to a smaller program in terms of size.
In the main() program, we will declare an unsigned char variable named value. We will send this value to
PORTB, to be displayed by the 4 LEDs (representing the bit locations) for 1 second. Then value will be
incremented by 1 and displayed again, starting from 0 (0x00) to 15 (0x0F). If value is more than 15, it will be
reset back to 0.
Code:
//4 LEDs at PORTB<3:0>
#include <pic.h>
#include "delay.c"
//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();
while(1){
PORTB = value;
//display value, LEDs will turn on/off,
corresponding to the binary value of the variable
value++;
//increment value by 1
if (value > 0x0F)
//if value is more than 15
value = 0x00; //restart back to 0
delay();
}
}
//CONFIG bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
//initialize PORTB
void init_LED(void)
{
PORTB &= ~0xFF;
TRISB &= ~0xFF;
}
//0.5 second delay
void delay(void)
{
DelayMs(250);
DelayMs(250);
}
void main()
{
unsigned char value = 0x00;
init_LED();
while(1){
PORTB = 0x01;
delay();
PORTB = 0x02;
delay();
PORTB = 0x04;
delay();
PORTB = 0x08;
delay();
PORTB = 0x10;
delay();
PORTB = 0x20;
delay();
PORTB = 0x40;
delay();
PORTB = 0x80;
delay();
PORTB = 0x40;
delay();
PORTB = 0x20;
delay();
PORTB = 0x10;
delay();
PORTB = 0x08;
delay();
PORTB = 0x04;
delay();
PORTB = 0x02;
delay();
PORTB = 0x01;
delay();
}
}
The delay is 0.5 seconds. You might want to change it to around 50 ms, para mas enjoy yung simulation
NOWDT
XT
NOPUT
NOPROTECT
#use delay(clock=4000000)
void main()
{
set_tris_b(0x00);
output_b(0x00);
while(1)
{
output_b(0x01);
delay_ms(500);
output_b(0x02);
delay_ms(500);
output_b(0x04);
delay_ms(500);
output_b(0x08);
delay_ms(500);
output_b(0x10);
delay_ms(500);
output_b(0x20);
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"
//CONFIG bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
#define SHIFT_RIGHT
#define SHIFT_LEFT
0x00
0x01
//initialize PORTB
void init_LED(void)
{
PORTB &= ~0xFF;
TRISB &= ~0xFF;
}
//0.5 second delay
void delay(void)
{
DelayMs(250);
DelayMs(250);
}
void main()
{
unsigned char value = 0x01;
unsigned char direction;
init_LED();
direction = SHIFT_LEFT;
while(1)
{
//RB0 on --> RB1 --> .... RB7
if (direction == SHIFT_LEFT)
{
PORTB = value;
value = value << 1;
if (value == 0x80)
displayed is 0x80
direction = SHIFT_RIGHT;
delay();
//display to PORTB
//shift left
//check if next value to be
//reverse direction
}
//RB7 on --> RB6 --> .... RB0
if (direction == SHIFT_RIGHT)
{
PORTB = value;
value = value >> 1;
if (value == 0x01)
displayed is 0x01
direction = SHIFT_LEFT;
delay();
}
}
}
//display to PORTB
//shift right
//check if next value to be
//reverse direction
This is our 3rd "scrowling LED" program. The LED is not drive directly by the PORTB pins. The LED will
turn on when the output pin is pulled low to ground level.
Figure 17. The LED anode are pulled up to +5V via 330 resistor,
anode to PORTB pins
PORTB = value;
//display to PORTB
to this
Code:
PORTB = ~value;
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.
//0
//1
PORTB = ~0x5B;
delay();
PORTB = ~0x4F;
delay();
PORTB = ~0x66;
delay();
PORTB = ~0x6D;
delay();
PORTB = ~0x7D;
delay();
PORTB = ~0x07;
delay();
PORTB = ~0xFF;
delay();
PORTB = ~0x6F;
delay();
//2
//3
//4
//5
//6
//7
//8
//9
}
}
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)
{
~0x3F;
//0
~0x06;
//1
~0x5B;
//2
~0x4F;
//3
~0x66;
//4
~0x6D;
//5
~0x7D;
//6
~0x07;
//7
~0xFF;
//8
~0x6F;
//9
value++;
//increment value
delay();
}
}
while(1)
{
PORTB = value;
value++;
}
}
delay();
The circuit above shows 2 common anode 7-segment displays sharing the pin connections (multiplexed) from
the 74LS47 IC. SEGMENT1 and SEGMENT2 can be disabled/enabled via the NPN transistors Q1 and Q2,
respectively. Q1 will conduct (and enable SEGMENT1) if RA0 is high, and Q2 will conduct (and enable
SEGMENT2) if RA1 is high
If we want to display the number "41", all we have to do is output '1' to PORTB<3:0>, enable SEGMENT1
(RA0 = 1) and disable SEGMENT2 (RA1=0). Then output '4' to PORTB<3:0>, enable SEGMENT2 (RA1 = 1)
and disable SEGMENT1 (RA0 = 0). We repeat the process ad infinitum.
Here is the sample code.
Code:
//PORTB<3:0> to 74LS47
//RA0 enable/disable SEGMENT1
//RA1 enable/disable SEGMENT2
#include <pic.h>
#include "delay.c"
//function prototype
void delay(void);
void main()
{
PORTB
TRISB
PORTB
TRISA
&=
&=
&=
&=
~0x0F;
~0x0F;
~0x03;
~0x03;
while(1)
{
PORTA = 0x01;
PORTB = 0x01;
delay();
PORTA = 0x02;
PORTB = 0x04;
delay();
}
}
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
This is an improved sample program of the previous one
Code:
//PORTB<3:0> to 74LS47
//RA0 enable/disable SEGMENT1
//RA1 enable/disable SEGMENT2
#include <pic.h>
#include "delay.c"
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
//macro definitions, used for easier code readability, etc..
#define ENABLE_SEGMENT1
(PORTA=0x01)
#define ENABLE_SEGMENT2
(PORTA=0x02)
//function prototype
void delay(void);
void main()
{
unsigned char display_value = 41;
unsigned char tens_digit;
unsigned char ones_digit;
//number to be displayed
//holds the tens digit of display_value
//holds the ones digit of display_value
tens_digit = display_value/10;
ones_digit = display_value - (tens_digit * 10);
while(1)
{
ENABLE_SEGMENT1;
PORTB = ones_digit;
delay();
//display '1'
//500ms delay
ENABLE_SEGMENT2;
PORTB = tens_digit;
delay();
//display '4'
//500ms delay
}
}
void delay(void)
{
DelayMs(250);
DelayMs(250);
}
Up to this time, we usually define our user-defined functions before the main() function. If you will define
your functions below/after the main() function, you need to include a function prototype before main() so that
the compiler will not flag a compile error when it encounters the function name inside the main().
I also included macros, for some fancy naming conventions and improve readbility (it has more uses actually
)
Code:
#define ENABLE_SEGMENT1
#define ENABLE_SEGMENT2
(PORTA=0x01)
(PORTA=0x02)
//PORTB<3:0> to 74LS47
//RA0 enable/disable SEGMENT1
//RA1 enable/disable SEGMENT2
#include <pic.h>
#include "delay.c"
//configuration fuse bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
//macro definitions
#define ENABLE_SEGMENT1
#define ENABLE_SEGMENT2
(PORTA=0x01)
(PORTA=0x02)
//function prototype
void delay(void);
void main()
{
unsigned char display_value = 0x00;
displayed to 7-segment
unsigned char tens_digit;
unsigned char ones_digit;
while(1)
{
tens_digit = display_value/10;
ones_digit = display_value - (tens_digit * 10);
//tens digit
//ones digit
ENABLE_SEGMENT1;
PORTB = ones_digit;
delay();
//enable SEGMENT1
//display ones digit
//250ms delay
ENABLE_SEGMENT2;
PORTB = tens_digit;
//enable SEGMENT2
//display tens digit
display_value++;
NOTE: The previous programs are not fully working (usable on an actual application), i included them here to
demonstrate the basic concepts like enabling/disabling segments and extracting digits for display
Here is another more improved program. The program will still count from 0 to 99, with approximately 1
second per update.
Code:
//PORTB<3:0> to 74LS47
//RA0 enable/disable SEGMENT1
//RA1 enable/disable SEGMENT2
#include <pic.h>
#include "delay.c"
//configuration fuse bits
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
//global variables
unsigned char display_value = 0x00;
displayed to 7-segment
unsigned char tens_digit;
unsigned char ones_digit;
//function prototypes
void update_value(unsigned char display_value);
void display(void);
void main()
{
unsigned char temp=0x00;
update_value(display_value);
//initial update
while(1)
{
display();
segments
//display display_value to 7-
temp++;
if (temp == 9)
elapsed
{
display_value++;
//increment value
update_value(display_value);
value
temp = 0x00;
}
}
}
//display to 7segment
void display(void)
{
PORTA = 0x01;
PORTB = ones_digit;
DelayMs(50);
//enable SEGMENT1
//display '1'
//50ms delay
PORTA = 0x02;
PORTB = tens_digit;
DelayMs(50);
//enable SEGMENT2
//display '4'
//50ms delay
}
//extract the tens and ones digit
void update_value(unsigned char display_value)
{
tens_digit = display_value/10;
ones_digit = display_value - (tens_digit * 10);
}
The switching between the 2 segments is now very rapid and will not display appropriately on a GIF figure, so i
will not post a GIF image.
Build the program and simulate in Proteus.
You may now want to test this program on an actual hardware. To "smoothen" (eliminate noticeable flicker) the
display, just reduce the DelayMs() argument inside the display() function. To control the time between
increment of display_value just change the limit of the temp variable inside the while(1) loop. (ie. if (temp ==
18))
sample use:
Code:
void main()
{
//initialization codes here
init_uart();
while(1)
{
puts("sevenstring impakta\r");
DelayMs(250);
}
Example program. It will toggle an LED at RB0 everytime character 'a' or 'A' is receive
Code:
#include <pic.h>
void interrupt isr(void)
{
unsigned char c;
if (RCIF)
{
c = RCREG;
if (c == 'a' || c == 'A' )
RB0 ^= 1;
if(OERR)
{
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;
}
Today, you are expecting a visitor. This visitor called you up the day before and told you that he will drop by at your
house to talk about a few things. However, he forgot to tell you what time he will come.
If you are really expecting this person to come any moment, you might keep on glancing at the window many times
during the hour. Or you might totally stop doing house chores and decided to just stand near the window to wait for the
visitor, which could arrived any moment or maybe after the next 4 hours. Now this is rather silly, isnt it? You might ask
me if there is a better way to wait for a house visitor than polling/checking/looking at the window.
I know, I know. The best thing you would rather do is to keep on working while waiting for the (*drums!) (tada!) DOOR
BELL to ring.
Now, this wonderful invention, the door bell (an interrupting mechanism) allows you to be INTERRUPTED by a visitor
standing outside your gate. Genius, right?
So you have two ways of knowing when the visitor arrives:
(1) polling (waiting/glancing for hours at the window
) and
(2) interrupts (using doorbell) (for the sake of discussion, lets assume you can't do both. If you insist you can, i'll force you to explain WHY there is GRAVITY
)
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.
In our next several examples, we will demonstrate how to use TMR0 as an interrupt source.
The PIC16F84a has an 8-bit timer module/peripheral, called TIMER0 (or TMR0).
We can view the TMR0 simply as special kind of register and it can hold values from 0 to 255 (since it is 8-bit wide, 2 ^ 8
= 256).
When TMR0 is initialized properly in Timer mode, it can hold a starting value of 0 and increment every instruction clock
cycle (1 instruction clock cycle = Fosc/4) up to 255, where it then reloads/overflows back to 0 and start
incrementing/counting all over again until power is removed from the PIC or when TMR0 is disabled via software. When
TMR0 overflows, it can generate/trigger an interrupt to notify the CPU. The CPU will then execute a special code (called
an interrupt service routine) in response to this interrupt.
As an example, TMR0 will count from 0 - 255 every 0.256 ms when the PIC16F84a has a 4Mhz crystal oscillator.
(system clock)
and
(instruction clock)
also
(1 instruction clock period)
Therefore,
The TMR0 is useful in applications that requires timing reference. For example, you might want to read an IR sensor that
is connected at RB0 pin, say every 10 millisecond. You may then configure TMR0 to overflows every 10 millisecond and
also configure it as an interrupt source. When the TMR0 overflows, it will trigger an interrupt. The CPU will then respond
to this interrupt by executing a code (also known as interrupt service routine, ISR) that reads the RB0 pin.
Remember that TMR0 increments independent of the main program running in the PIC MCU. The program simply has to
initialize TMR0 at the start and respond to it occasionally via the ISR when the TMR0 interrupt triggers.
TMR0 is configured via the INTCON and OPTION special function registers. The bits relevant to TMR0 operation are
highlighted below.
Let's create a sample program that will demonstrate how to use the TMR0 as an interrupt source. This program
is rather simple. It will simply toggle (turn on/off) an LED connected at RB0 pin every time TMR0 overflow
every 0.256 ms. Below is the sample program.
Hi-Tech C Code:
Code:
#include <pic.h>
__CONFIG(XT & WDTDIS & PWRTDIS & UNPROTECT);
void main()
{
//initialize RB0
TRISB &= ~0x01;
PORTB &= ~0x01;
//initialize TMR0
INTCON &= ~0x80;
OPTION &= ~0x20;
OPTION |= 0x08;
increment
//EVERY clock cycle
while(1);
//infinite loop
//do nothing other than to wait for the TMR0 interrupt
//to occur, then execute the ISR (tmr0_isr)
//toggle LED
//clear Timer0 interrupt flag
is the interrupt service routine (ISR for short). It is simply a C function with the keyword interrupt in the
function header. The function name can be any name, though it should be descriptive. Using the name tmr0_isr
is an obvious choice. There are two lines inside the ISR. The first line will toggle the RB0 pin. The second line
will clear the TMR0 interrupt flag (T0IF = 0). Remember that when the TMR0 interrupt is triggered, the T0IF
bit is set so at the end of the ISR it should also be cleared.
Unlike ordinary function that are executed via explicit function calls from the program, the ISR is executed
automatically (or invoked by the interrupt controller) when the interrupt triggers.
TMR0 = 0x00;
INTCON &= ~0x04;
INTCON |= 0x20;
INTCON |= 0x80;
= 1)
0)
We must set the appropriate values of the bits in the INTCON and OPTION registers.
It is advisable to disable triggering of all interrupts when we are configuring or initializing an interrupt source
like the TMR0. So we have the line
Code:
INTCON &= ~0x80;
This will clear the GIE bit (INTCON<7> bit) and will disable all interrupts.
The next line
Code:
OPTION &= ~0x20;
will select the instruction cycle clock (Fosc/4) as the input to TMR0. In other words, clearing the TOCS bit
(OPTION<5> bit) will select Fosc/4 as the clock source for the TMR0. When the T0CS bit is 0, TMR0 is said
to be in Timer mode. If T0CS is 1, TMR0 is in Counter mode. We are using Timer mode in this sample
program.
This line
Code:
OPTION |= 0x08;
will assign the prescaler to the Watch Dog Timer of the PIC.
The above line means that the TMR0 will increment EVERY clock cycle since we are not using the prescaler.
Take note in advance that it is possible to increment the TMR0 every 2,4,8,..256 clock cycles by choosing the
appropriate prescaler values in the OPTION register. By using prescaler, we can increase the TMR0 overflow
period (i.e. slow down the TMR0 overflow period). We will have examples on using TMR0 with prescaler
later.
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;
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.
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);
//toggle LED
void main()
{
//initialize RB0
TRISB &= ~0x01;
RB0 = 0;
//initialize TMR0
GIE = 0;
T0CS = 0;
PSA = 1;
T0IF = 0;
T0IE = 1;
GIE = 1;
while(1);
//infinite loop
//do nothing
//toggle LED
}
void init_TMR0(void)
{
GIE = 0;
T0CS = 0;
PSA = 1;
T0IF = 0;
T0IE = 1;
GIE = 1;
void main()
{
//initialize RB0
TRISB &= ~0x01;
RB0 = 0;
//initialize TMR0
init_TMR0();
while(1);
//infinite loop
//do nothing
//configuration fuses
void main()
{
set_tris_b(0x00);
output_low(PIN_B0);
setup_timer_0(RTCC_INTERNAL | RTCC_DIV_1);
enable_interrupts(INT_RTCC | GLOBAL);
//no prescaler
//enable TMR0 interrupt and
//enable all interrupts
Fosc/4
while(TRUE);
}