Appendix B: Examples

The purpose of this chapter is to provide basic information about microcontrollers that one needs to know in order to be able to use them successfully in practice. This chapter, therefore, does not contain any super interesting program or device schematic with amazing solutions. Instead, the following examples are more proof that program writing is neither a privilege nor a talent issue but the ability of simply putting puzzle pieces together using directives. Rest assured that design and development of devices mainly consists of the following method "test-correct-repeat". Of course, the more you are in it the more complicated it becomes since the puzzle pieces are put together by both children and first-class architects...

BASIC CONNECTING

As seen in the figure below, in order to enable the microcontroller to operate properly it is necessary to provide:

  • Power Supply;
  • Reset Signal; and
  • Clock Signal.
Basic Connecting

Clearly, it is about simple circuits, but it does not have to always be like that. If the target device is used for controlling expensive machines or maintaining vital functions, everything gets more and more complicated! However, this solution is sufficient for the time being...

POWER SUPPLY

Even though the PIC16F887 can operate at different supply voltages, why to test "Murphy's low"?! A 5DC power supply is shown above. The circuit, uses a cheap integrated three-terminal positive regulator, LM7805, provides high-quality voltage stability and quite enough current to enable microcontroller and peripheral electronics to operate normally (enough in this case means 1Amp).

RESET SIGNAL

In order that the microcontroller can operate properly, a logic one (VCC) must be applied on the reset pin it explains the connection pin-resistor 10K-VCC. The push-button connecting the reset pin MCLR to GND is not necessary. However, it is almost always provided because it enables the microcontroller safe return to normal operating conditions if something goes wrong. By pushing this button, 0V is brought to the pin, the microcontroller is reset and program execution starts from the beginning. The 10K resistor is there to allow 0V to be applied to the MCLR pin, via the push-button, without shorting the 5VDC rail to ground.

CLOCK SIGNAL

Even though the microcontroller has a built in oscillator, it cannot operate without external components which stabilize its operation and determine its frequency (operating speed of the microcontroller). Depending on which elements are in use as well as their frequencies, the oscillator can be run in four different modes:

  • LP - Low Power Crystal;
  • XT - Crystal / Resonator;
  • HS - High speed Crystal / Resonator; and
  • RC - Resistor / Capacitor.
Oscillator different modes

Why are these modes so important? Owing to the fact that it is almost impossible to make a stable oscillator which operates over a wide frequency range, the microcontroller must know which crystal is connected in order that it can adjust the operation of its internal electronics to it. This is why all programs used for chip loading contains an option for oscillator mode selection. See above figure.

Quartz resonator

When a quartz crystal is used for frequency stabilization, the built in oscillator operates at a very precise frequency which is isolated from changes in temperature and voltage power supply as well. This frequency is normally labelled on the microcontroller package.

Apart from the crystal, capacitors C1 and C2 must be also connected as per the schematic below. Their capacitance is not of great importance, therefore, the values provided in the table should be considered as a recommendation rather than a strict rule.

Quartz resonator
Ceramic resonator

Ceramic resonator is cheaper, but very similar to quartz by its function and the way of operating. This is why the schematics illustrating their connection to the microcontroller are identical. However, the capacitor value is a bit different in this case due to different electric features. Refer to the table.

Ceramic resonator

These oscillators are used when it is not necessary to have extremely precise frequency.

RC oscillator

If the operating frequency is not of importance then there is no need for additional expensive components for stabilization. Instead, a simple RC network, as shown in the figure below, will be enough. Since only the input of the local oscillator input is in use here, the clock signal with frequency Fosc/4 will appear on the OSC2 pin. Furthermore, that frequency becomes operating frequency of the microcontroller, i.e. the speed of instruction execution.

RC oscillator
External oscillator

If it is required to synchronize the operation of several microcontrollers or if for some reason it is not possible to use any of the previous schematics, a clock signal may be generated by an external oscillator. Refer to the figure below.

External oscillator

ADDITIONAL COMPONENTS

Regardless of the fact that the microcontroller is a product of modern technology, it is of no use without being connected to additional components. Simply, the appearance of voltage on the microcontroller pins mean nothing if not used for performing certain operations (turn something on/off, shift, display etc.).

This section intentionally covers only the most commonly used additional components in practice such as resistors, transistors, LED diodes, LED displays, LCD displays and RS232 communication circuits.

SWITCHES AND PUSH-BUTTONS

There is nothing simpler than switches and push-buttons! This is definitely the simplest way of detecting the appearance of a voltage on the microcontroller input pin and there is no need for additional explanation of how these components operate. Nevertheless, it is not so simple in practice... Then, what is it all about?

Debounce effect

It is about contact bounce- a common problem with mechanical switches. When the contacts strike together, their momentum and elasticity act together to cause bounce. The result is a rapidly pulsed electrical current instead of a clean transition from zero to full current. Generally, it mostly occurs due to vibrations, slight rough spots and dirt between contacts. This effect is usually unnoticeable when using these components in everyday life because the bounce happens too quickly to affect most equipment, but causes problems in some analogue and logic circuits that respond fast enough to misinterpret the on-off pulses as a data stream. Anyway, the whole process does not last long (a few micro- or milliseconds), but long enough to be registered by the microcontroller. When using only a push-button as a pulse counter, errors occurs in almost 100% of cases!

This problem may be easily solved by connecting a simple RC circuit to surpress quick voltage changes. Since the bounce period is not defined, the values of components are not precisely determined. In most cases, it is recommended to use the values as shown in figure below.

RC circuit

If complete stability is needed then radical measures should be taken! The output of the circuit, shown in figure below (RS flip-flop), will change its logic state only after detecting the first pulse triggered by contact bounce. This solution is more expensive (SPDT switch), but the problem is definitely solved!

RS flip-flop circuit

In addition to these hardware solutions, there is also a simple software solution. When a program tests the state of an input pin and detects a change, the check should be done one more time after a certain delay. If the program confirms the change, it means that a switch/push-button has changed its position. The advantages of such solution are obvious: it is free of charge, effects of noises are eliminated and it can be applied to the poorer quality contacts as well.

RELAY
Relays

A relay is an electrical switch that opens and closes under the control of another electrical circuit. It is therefore connected to output pins of the microcontroller and used to turn on/off high-power devices such as motors, transformers, heaters, bulbs, etc. These devices are almost always placed away from the boards sensitive components. There are various types of relays, but all of them operate in the same way. When a current flows through the coil, the relay is operated by an electromagnet to open or close one or many sets of contacts. Similar to optocouplers, there is no galvanic connection (electrical contact) between input and output circuits. Relays usually demand both higher voltage and current to start operation but there are also miniature ones that can be activated by a low current directly obtained from a microcontroller pin.

Connecting relay

This figure shows the most commonly used solution.

In order to prevent the appearance of high voltage self-induction caused by a sudden stop of current flow through the coil, an inverted polarized diode is connected in parallel to the coil. The purpose of this diode is to "cut off" the voltage peak.

LED DIODES

You probably know all you need to know about LED diodes, but we should also think of the younger generations...How to destroy a LED?! Well...Very simple.

LED DIODE
Quick burning

Like any other diode, LEDs have two ends an anode and a cathode. Connect it properly to a power supply voltage. The diode will happily emit light. Turn it upside down and apply the same power supply voltage (even for a moment). It will not emit light- NEVER AGAIN!

Slow burning

There is a nominal, i.e. maximum current determined for every LED which should not be exceeded. If it happens, the diode will emit more intensive light, but not for a long time!

Something to remember

Similar to the previous example, all you need to do is to discard a current limiting resistor shown below. Depending on power supply voltage, the effect might be spectacular!

Something to remember
LED DISPLAY

Basically, LED display is nothing more than several LEDs moulded in the same plastic case. There are many types of displays composed of several dozens of built in diodes which can display different symbols. The most commonly used is so called 7-segment display. It is composed of 8 LEDs- 7 segments are arranged as a rectangle for symbol displaying and there is an additional segment for decimal point displaying. In order to simplify connection, anodes or cathodes of all diodes are connected to the common pin so that there are common anode displays and common cathode displays, respectively. Segments are marked with the letters from a to g, plus dp, as shown in figure below. On connecting, each diode is treated separately, which means that each must have its own current limiting resistor.

LED DISPLAY

Here are a few important things that one should pay attention to when buying LED displays:

  • Depending on whether anodes or cathodes are connected to the common pin, there are common anode displays and common cathode displays. The figure above shows a common anode display. Looking at physical features, there is no difference between these displays at all so it is recommended to check carefully prior installation which of them is in use;
  • For each microcontroller pin, there is a maximum current limitation it can receive or give. Because of this, if several displays are connected to the microcontroller it is rec ommended to use so called Low current LEDs using only 2mA for operation; and
  • Display segments are usually marked with the letters from a to g, but there is no fast rule indicating to which micro controller pins they should be connected. For this reason it is very important to check connecting prior to commencing program writing or designing a device.

Displays connected to the microcontroller usually occupy a large number of valuable I/O pins, which can be a big problem especially when it is needed to display multi-digital numbers. The problem is more than obvious if, for example, it is needed to display two 6-digit numbers (a simple calculation shows that 96 output pins are needed in this case)! This problem has a solution called MULTIPLEXING.

Here is how an optical illusion based on the same operating principle as a film camera is made. Only one digit at a time is active, but they change their state so quickly that one gets impression that all digits of a number are active simultaneously.

MULTIPLEXING

Here is an explanation on the figure above. First a byte representing units is applied on a microcontroller port and a transistor T1 is activated simultaneously. After a while, the transistor T1 is turned off, a byte representing tens is applied on a port and transistor T2 is activated. This process is being cyclically repeated at high speed for all digits and corresponding transistors.

A disappointing fact which indicates that the microcontroller is just a kind of miniature computer designed to understand only the language of zeros and ones is fully expressed when displaying any digit. Namely, the microcontroller does not know what units, tens or hundreds are, nor what ten digits we are used to look like. Therefore, each number to be displayed must go through the following procedure:

First of all, in a particular subroutine a multi-digital number must be split into units, tens etc. Then, these must be stored in special bytes each. Digits get recognizable format by performing "masking". In other words, a binary format of each digit is replaced by a different combination of bits using a simple subroutine. For example, the digit 8 (0000 1000) is replaced by binary number 0111 1111 in order to activate all LEDs displaying digit 8. The only diode remaining inactive in this case is reserved for the decimal point.

If a microcontroller port is connected to the display in a way that bit 0 activates segment "a", bit 1 activates segment "b", bit 2 segment "c" etc., then the table below shows the mask for each digit.

Masking
Digits to display Display Segments
dp a b c d e f g
0 0 1 1 1 1 1 1 0
1 0 1 1 0 0 0 0 0
2 0 1 1 0 1 1 0 1
3 0 1 1 1 1 0 0 1
4 0 0 1 1 0 0 1 1
5 0 1 0 1 1 0 1 1
6 0 1 0 1 1 1 1 1
7 0 1 1 1 0 0 0 0
8 0 1 1 1 1 1 1 1
9 0 1 1 1 1 0 1 1

In addition to digits from 0 to 9, there are some letters- A, C, E, J, F, U, H, L, b, c, d, o, r, t- that can be also displayed by means of the appropriate masking.

In the event that the common anode displays are used, all ones contained in the previous table should be replaced by zeros and vice versa. Additionally, NPN transistors should be used as drivers as well.

OPTOCOUPLER

An optocoupler is a device commonly used to galvanically separate microcontroller electronics from any potentially dangerous current or voltage in its surroundings. Optocouplers usually have one, two or four light sources (LED diodes) on their input while on their output, opposite to diodes, there is the same number of elements sensitive to light (phototransistors, photo-thyristors or photo-triacs). The point is that an optocoupler uses a short optical transmission path to transfer a signal between elements of circuit, while keeping them electrically isolated. This isolation makes sense only if diodes and photo-sensitive elements are separately powered. In this way, the microcontroller and expensive additional electronics are completely protected from high voltage and noises which are the most common cause of destroying, damaging or unstable operation of electronic devices in practice. The most frequently used optocouplers are those with phototransistors on their outputs. Additionally, optocouplers with internal base-to-pin 6 connection (there are also optocouplers without it), the base may be left unconnected.

Optocoupler Usage

The R/C network represented by the broken line in the figure above denotes optional connection which lessens the effects of noises by eliminating very short pulses.

LCD DISPLAY

This component is specifically manufactured to be used with microcontrollers, which means that it cannot be activated by standard IC circuits. It is used for displaying different messages on a miniature liquid crystal display. The model described here is for its low price and great capabilities most frequently used in practice. It is based on the HD44780 microcontroller (Hitachi) and can display messages in two lines with 16 characters each. It displays all the letters of alphabet, Greek letters, punctuation marks, mathematical symbols etc. In addition, it is possible to display symbols made up by the user. Other useful features include automatic message shift (left and right), cursor appearance, LED backlight etc.

LCD DISPLAY
LCD DISPLAY

Along one side of a small printed board there are pins used for connecting to the microcontroller. There are in total of 14 pins marked with numbers (16 if the backlight is built in). Their function is described in the table bellow:

Function Pin Number Name Logic State Description
Ground 1 Vss - 0V
Power supply 2 Vdd - +5V
Contrast 3 Vee - 0 - Vdd
Control of operating 4 RS 0
1
D0 – D7 are interpreted as commands
D0 – D7 are interpreted as data
5 R/W 0
1
Write data (from controller to LCD)
Read data (from LCD to controller)
6 E 0
1
From 1 to 0
Access to LCD disabled
Normal operating
Data/commands are transferred to LCD
Data / commands 7 D0 0/1 Bit 0 LSB
8 D1 0/1 Bit 1
9 D2 0/1 Bit 2
10 D3 0/1 Bit 3
11 D4 0/1 Bit 4
12 D5 0/1 Bit 5
13 D6 0/1 Bit 6
14 D7 0/1 Bit 7 MSB
LCD screen

The LCD screen consists of two lines with 16 characters each. Every character consists of 5x8 or 5x11 dot matrix. This book covers the 5x8 character display, which is indeed the most commonly used.

LCD screen

Display contrast depends on the power supply voltage and whether messages are displayed in one or two lines. For this reason, varying voltage 0-Vdd is applied on the pin marked as Vee. Trimmer potentiometer is usually used for that purpose. Some LCD displays have built in backlight (blue or green diodes). When used during operation, a current limiting resistor should be serially connected to one of the pins for backlight (similar to LED diodes).

Connecting LCD

If there are no characters displayed or if all of them are dimmed when the display is switched on, the first thing that should be done is to check the potentiometer for contrast adjustment. Is it properly adjusted? The same applies if the mode of operation has been changed (writing in one or two lines).

LCD Memory

LCD display contains three memory blocks:

  • DDRAM - Display Data RAM;
  • CGRAM - Character Generator RAM; and
  • CGROM - Character Generator ROM.
DDRAM Memory

DDRAM memory is used for storing characters that should be displayed. The size of this memory is sufficient for storing 80 characters. Some memory locations are directly connected to the characters on display.

It works quite simply: it is enough to configure the display to increment addresses automatically (shift right) and set the starting address for the message that should be displayed (for example 00 hex).

After that, all characters sent through lines D0-D7 will be displayed as a message format we are used to- from left to right. In this very case, displaying starts from the first field of the first line because the address is 00 hex. If more than 16 characters are sent then all of them will be memorized, but only the first sixteen characters will be visible. In order to display the rest of them, a shift command should be used. Virtually, everything looks as if the LCD display is a window which shifts left-right over memory locations containing different characters. In reality, this is how the effect of message shifting on the screen has been created.

DDRAM Memory

If the cursor is on, it appears at the location which is currently addressed. In other words, when a character appears at the cursor position, it will automatically move to the next addressed location.

This is a sort of RAM memory so data can be written to and read from it, but its contents is irretrievably lost upon the power goes off.

CGROM Memory

CGROM memory contains the default character map with all characters that can be displayed on the screen. Each character is assigned to one memory location:

Default character map

The addresses of CGROM memory locations match the characters of ASCII. If the program being currently executed encounters a command "send character P to port" then the binary value 0101 0000 appears on the port. This value is the ASCII equivalent to the character P. It is then written to LCD, which results in displaying the symbol from the 0101 0000 location of CGROM. In other words, the character "P" is displayed. This applies to all letters of the alphabet (capitals and small), but not to the numbers!

As seen on the previous map, addresses of all digits are pushed forward by 48 relative to their values (digit 0 address is 48, digit 1 address is 49, digit 2 address is 50 etc.). Accordingly, in order to display digits correctly it is necessary to add a decimal number 48 to each of them prior to sending them to LCD.

What is ASCII? From their inception till today, computers can recognize only numbers, but not letters. It means that all data a computer swaps with a peripheral device has a binary format even though the same is recognized by the man as letters (The keyboard is an excellent example)! It’s as simple as that- every character matches the unique combination of zeroes and ones. ASCII is character encoding based on the English alphabet. ASCII code specifies a correspondence between standard character symbols and their numerical equivalents.

LCD Basic Commands

All data transferred to LCD through the outputs D0-D7 will be interpreted as a command or a data, which depends on the pin RS logic state:

RS = 1 - Bits D0 - D7 are addresses of the characters to be displayed. LCD processor addresses one character from the character map and displays it. The DDRAM address specifies the location on which the character is to be displayed. This address is defined prior character transfer or the address of the previously transferred character is automatically incremented.

RS = 0 - Bits D0 - D7 are commands which determine display mode.

The commands recognized by the LCD are listed in table below:

Command RS RW D7 D6 D5 D4 D3 D2 D1 D0 Execution Time
Clear display 0 0 0 0 0 0 0 0 0 1 1.64mS
Cursor home 0 0 0 0 0 0 0 0 1 x 1.64mS
Entry mode set 0 0 0 0 0 0 0 1 I/D S 40uS
Display on/off control 0 0 0 0 0 0 1 D U B 40uS
Cursor/Display Shift 0 0 0 0 0 1 D/C R/L x x 40uS
Function set 0 0 0 0 1 DL N F x x 40uS
Set CGRAM address 0 0 0 1 CGRAM address 40uS
Set DDRAM address 0 0 1 DDRAM address 40uS
Read "BUSY" flag (BF) 0 1 BF DDRAM address -
Write to CGRAM or DDRAM 1 0 D7 D6 D5 D4 D3 D2 D1 D0 40uS
Read from CGRAM or DDRAM 1 1 D7 D6 D5 D4 D3 D2 D1 D0 40uS
I/D 1 = Increment (by 1)         R/L 1 = Shift right
    0 = Decrement (by 1)             0 = Shift left
    
S 1 = Display shift on           DL 1 = 8-bit interface
  0 = Display shift off             0 = 4-bit interface
  
D 1 = Display on                 N 1 = Display in two lines
  0 = Display off                  0 = Display in one line
  
U 1 = Cursor on                  F 1 = Character format 5x10 dots
  0 = Cursor off                   0 = Character format 5x7 dots

B 1 = Cursor blink on            D/C 1 = Display shift
  0 = Cursor blink off               0 = Cursor shift
What is Busy flag ?

Compared to the microcontroller, the LCD is an extremely slow component. Because of this, it was necessary to provide a signal which would, upon command execution, indicate that the display is ready for the next piece of data. That signal, called the busy flag, can be read from the line D7. When the voltage on this line is 0V (BF=0), the display is ready to receive new data.

LCD Connecting

Depending on how many lines are used for connecting the LCD to the microcontroller, there are 8-bit and 4-bit LCD modes. The appropriate mode is selected at the beginning of the operation in this process called "initialization". 8-bit LCD mode uses outputs D0-D7 to transfer data as explained on the previous page.

The main purpose of 4-bit LED mode is to save valuable I/O pins of the microcontroller. Only 4 higher bits (D4-D7) are used for communication, while others may be unconnected. Each piece of data is sent to the LCD in two steps- four higher bits are sent first (normally through the lines D4-D7) and four lower bits are sent afterwards. Initialization enables the LCD to link and interpret received bits correctly.

Connecting LCD to microcontroller

Data is rarely read from the LCD (it is mainly transferred from the microcontroller to LCD) so it is often possible to save an extra I/O pin by simple connecting R/W pin to the Ground. Such saving has its price. Messages will be normally displayed, but it will not be possible to read the busy flag since it is not possible to read the display as well. Fortunately, there is a simple solution. After sending a character or a command it is important to give the LCD enough time to do its job. Owing to the fact that the execution of the slowest command lasts for approximately 1.64mS, it will be sufficient to wait approximately 2mS for LCD.

LCD Initialization

The LCD is automatically cleared when powered up. It lasts for approximately 15mS. After this, display is ready for operation. The mode of operation is set by default. It means that:

  1. Display is cleared.
  2. Mode
    DL = 1 Communication through 8-bit interface
    N = 0 Messages are displayed in one line
    F = 0 Character font 5 x 8 dots
  3. Display/Cursor on/off
    D = 0 Display off
    U = 0 Cursor off
    B = 0 Cursor blink off
  4. Character entry
    ID = 1 Displayed addresses are automatically incremented by 1
    S = 0 Display shift off

Automatic reset is mostly done without any problems. Mostly, but not always! If for any reason the power supply voltage does not reach full value within 10mS, the display will start performing completely unpredictably. If the voltage supply unit is not able to meet that condition or if it is needed to provide completely safe operation, the process of initialization is applied. Initialization, among other things, causes a new reset enabling display to operate normally.

Automatic reset is mostly done without any problems. Mostly, but not always! If for any reason power supply voltage does not reach full value within 10mS, display will start performing completely unpredictably. If voltage supply unit is not able to meet that condition or if it is needed to provide completely safe operation, the process of initialization is applied. Initialization, among other things, causes a new reset enabling display to operate normally.

Refer to the figure below for the procedure on 8-bit initialization:

8-bit initialization

It is not a mistake! In this algorithm, the same value is transferred three times in a row.

In case of 4-bit initialization, the procedure is as follows:

4-bit initialization

EXAMPLE 1

Writing header and configuring I/O pins

The only purpose of this program is to turn on a few LED diodes on port B. It is nothing special. Anyway, use this example to study what a real program looks like. The figure below shows a connection scheme, while the program is on the next page.

Configuring I/O pins

When switching on, every other LED diode on the port B emits light. That is enough to indicate that the microcontroller is properly connected and operates normally.

This example gives the description of a correctly written header and a few initial directives. They represent a part of the program used in all programs described in this book. To skip repetitiveness, it will not be written in the following examples, but is considered to be at the beginning of every program (marked as a "Header").

Example 1 - Writing header and configuring I/O pins

The purpose of the header and initial directives is briefly described below.

Header:

The header is placed at the beginning of the program and gives basic information in the form of comments (name of the program, release date etc.). Don't be deluded into thinking that after a few months you will know what that program is about and why it is saved in your computer.

Initial directives:

list p=16f887

This directive defines processor to execute a program.

#include <p16f887.inc>

It enables the compiler to access the document p16f887.inc (If you have MPLAB installed, it is placed by default on C:\Program files\Microchip\MPASM Suite). Every SFR register contained in this document, as well as every bit, has its own name and address. If the program reads for example:

bsf INTCON, GIE

It means that the GIE bit of the INTCON register should be set. Instruction, as such, makes no sense to the compiler. It has to access the ".inc" document in order to know that the seventh bit of the SFR at the address 000B hex should be set.

Inside INC file

errorlevel -302

This is a "cosmetic" directive which disables the irritating message "Register in operand not in ..." to appear at the end of every compiling process. It is not necessary, but useful.

__config

This directive is used to include config word in the program upon compiling. It is not necessary because the same operation is performed by software for loading program into chip. However, do you have any idea which software will be used by the end user? What options will be set by default? You are the end user?! Do you know which program you will be using for MCU programming next year? Make life easier for yourself, take this directive as a necessary one and include it in your program.

EXAMPLE 2

Using program loop and internal oscillator LFINTOSC

This is a continuation of the previous example, but deals with a bit more complicated problem... The idea is to make the LED diodes on the port B blink. A simple thing at first glance! It is enough to periodically change logic state on the port B. In this case, numbers 01010101 and 10101010 are selected to change in the following way:

  1. Set binary combination 01010101 on port B;
  2. Remain in loop1;
  3. Replace existing bits combination on port B with 10101010;
  4. Remain in loop2; and
  5. Return to the step 1 and repeat the whole procedure.

Do you know how fast this should be done? It would be possible to observe changes on port B only if, apart from the delays provided in loop1 and loop2, the whole process is slowed down approximately 250 times more. Because of this, the microcontroller uses internal oscillator LFINTOSC with the frequency of 31kHz instead of the external oscillator with quartz crystal (8MHz).

You have noticed that the clock signal source has changed "on the fly". If you want to make sure of it, remove quartz crystal prior to switching the microcontroller on. What will happen? The microcontroller will not start operating because the config word loaded with the program requires the use of the crystal on switching on. If you remove the crystal later during the operation, it will not affect the microcontroller at all!

Example 2 - Using program loop and internal oscillator LFINTOSC
Example 2:
;************************************************************************
;                    Header
;************************************************************************
;************* DEFINING VARIABLES ***************************************
       cblock  0x20            ; Block of variables starts at address 20h
       counter1                ; Variable "counter1" at address 20h
       endc
;************************************************************************
       org     0x0000          ; Address of the first program instruction
        
       banksel OSCCON          ; Selects memory bank containing
                               ; register OSCCON
       bcf     OSCCON,6        ; Selects internal oscillator LFINTOSC with
       bcf     OSCCON,5        ; the frequency of 31KHz
       bcf     OSCCON,4
       bsf     OSCCON,0        ; Microcontroller uses internal oscillator
       
       banksel TRISB           ; Selects bank containing register TRISB
       clrf    TRISB           ; All port B pins are configured as outputs
       banksel PORTB           ; Selects bank containing register PORTB
       
loop
       movlw   B'01010101'     ; Binary number 01010101 is written to W
       movwf   PORTB           ; Number is moved to PORTB
       movlw   h'FF'           ; Number hFF is moved to W
       movwf   counter1        ; Number is moved to variable "counter1"
       
loop1
       decfsz  counter1        ; Variable "counter1" is decremented by 1
       goto    loop1           ; If result is 0, continue. If not,
                               ; remain in loop1
                               
       movlw   B'10101010'     ; Binary number 10101010 is moved to W
       movwf   PORTB           ; Number is moved to PORTB
       movlw   h'FF'           ; Number hFF is moved to W
       movwf   counter1        ; Number is moved to variable "counter1"
       
loop2
       decfsz  counter1        ; Variable "counter1" is decremented by 1
       goto    loop2           ; If result is 0, continue. If not,
                               ; remain in loop2
                               
       goto    loop            ; Go to label loop
       end                     ; End of program

EXAMPLE 3

Using nested loop

The connection scheme is again the same. To make this a bit more interesting, a different combination of port B bits change each other. And, that’s not all of course. As seen from the previous two examples, the microcontroller is very fast and often, it needs to be slowed down. The use of the built-in oscillator LF, as in example 2, is the last measure that should be applied. The problem is more often solved by using nested loops in a program. In this example, the variable "counter1" is decremented 255 times by 1 in the shorter loop1. Prior to leaving this loop, the program will countdown 255 times from 255 to 0. It means that between only two LED diode’s blink on the port, there are 255x255 pulses coming from the quartz oscillator. Precisely speaking, the number of pulses amounts to approximately 196 000 since it also takes some time to execute jump instructions and decrement instructions. Yes, it’s true, the microcontroller mostly waits and does nothing...

Example 3:
;******************* Header ***********************************************
;************* DEFINING VARIABLES *****************************************

       cblock      0x20          ; Block of variables starts at address 20h
       counter1                  ; Variable "counter1" at address 20h
       counter2                  ; Variable "counter2" at address 21h
       endc
;**************************************************************************
       org         0x0000        ; Address of the first program instruction
       
       banksel     TRISB         ; Selects bank containing register TRISB
       clrf        TRISB         ; Clears TRISB
       
       banksel     PORTB         ; Selects bank containing register PORTB
loop
       movlw       B'11110000'   ; Binary number 11110000 is moved to W
       movwf       PORTB         ; Number is moved to PORTB
       movlw       h'FF'         ; Number hFF is moved to W
       movwf       counter2      ; Number is moved to variable "counter2"
       
loop2
       movlw       h'FF'         ; Number hFF is moved to W
       movwf       counter1      ; Number is moved to "counter1"       
loop1
       decfsz      counter1      ; Decrements "counter1" by 1. If result is 0
       goto        loop1         ; skip next instruction
       
       decfsz      counter2      ; Decrements "counter2" by 1. If result is 0
       goto        loop2         ; skip next instruction
       
       movlw       B'00001111'   ; Binary number 00001111 is moved to W
       movwf       PORTB         ; Number is moved to PORTB
       movlw       h'FF'         ; Number hFF is moved to W
       movwf       counter2      ; Number is moved to variable "counter2"
       
loop4
       movlw       h'FF'         ; Number hFF is moved to W
       movwf       counter1      ; Number is moved to variable "counter1"
loop3
       decfsz      counter1      ; Decrements "counter1" by 1. If result is 0
                                 ; skip next instruction
       goto        loop3
       decfsz      counter2      ; Decrements "counter2" by 1. If result is 0
       goto        loop4         ; skip next instruction
       goto        loop          ; Jump to label loop
       end                       ; End of program

Example 4

Using timer TMR0 and Interrupts

If you have read the previous example, you would have noticed a disadvantage of providing delays using loops. In all these cases, the microcontroller is "captive" and does nothing. It simply waits for some time to pass. Such wasting of time is an unacceptable luxury and some other method should be applied.

Do you remember the story about the timers? About interrupts? This example makes links between them in a practical way. The schematic is still the same as well as the challenge. It is necessary to provide delay long enough to notice changes on a port. This time, the timer TMR0 with the assigned prescaler is used for that purpose. Interrupt occurs on every timer register overflow and interrupt routine increments the number in port B by 1. The whole procedure is performed "behind the scenes" of the whole process, which enables the microcontroller to do other things.

Example 4 - Using timer TMR0 and Interrupts

Pay attention to a few details:

  • Even though it is unnecessary in this case, the contents of the most important registers (W, STATUS and PCLATH) must be saved at the beginning of the interrupt routine;
  • Interrupt causes the appropriate flag bit to be automatically set and the GIE bit to be automatically cleared. At the end of the interrupt routine, do not forget to return these bits to the state they had prior to the interrupt occurring; and
  • At the end of the interrupt rutine, important registers should be given the original content.
Example 4:
;********************** Header **********************************************
;**************** DEFINING VARIABLES ****************************************

       cblock      0x20            ; Block of variables starts at address 20h
       w_temp                      ; Variable at address 20h
       pclath_temp                 ; Variable at address 21h
       status_temp                 ; Variable at address 22h
       endc
       
;************************ START OF PROGRAM **********************************
       org         0x0000          ; Address of the first program instruction
       goto        main            ; Go to label "main"
       
;************************ INTERRUPT ROUTINE *********************************
       org         0x0004          ; Interrupt vector
       movwf       w_temp          ; Saves value in register W
       movf        STATUS          ; Saves value in register STATUS
       movwf       status_temp
       movf        PCLATH          ; Saves value in register PCLATH
       movwf       pclath_temp
       
       banksel     PORTB           ; Selects bank containing PORTB
       incf        PORTB           ; Increments register PORTB by 1
       
       banksel     INTCON          ; Selects bank containing INTCON
       bcf         INTCON,TMR0IF   ; Clears interrupt flag TMR0IF
       
       movf        pclath_temp,w   ; PCLATH is given its original content
       movwf       PCLATH
       movf        status_temp,w   ; STATUS is given its original content
       movwf       STATUS
       swapf       w_temp,f        ; W is given its original content
       swapf       w_temp,w
        
       bsf         INTCON,GIE      ; Global interrupt enabled
       retfie                      ; Return from interrupt routine
       
;************************ MAIN PROGRAM **************************************
main                               ; Start of the main program

       banksel     ANSEL           ; Bank containing register ANSEL
       clrf        ANSEL           ; Clears registers ANSEL and ANSELH
       clrf        ANSELH          ; All pins are digital
       
       banksel     TRISB           ; Selects bank containing register TRISB
       clrf        TRISB           ; All port B pins are configured as outputs
       
       banksel     OPTION_REG      ; Bank containing register OPTION_REG
       bcf         OPTION_REG,T0CS ; TMR0 counts pulses from oscillator       
       bcf         OPTION_REG,PSA  ; Prescaler is assign to timer TMR0
       
       bsf         OPTION_REG,PS0  ; Prescaler rate is 1:256
       bsf         OPTION_REG,PS1
       bsf         OPTION_REG,PS2
       
       banksel     INTCON          ; Bank containing register INTCON
       bsf         INTCON,TMR0IE   ; TMR0 interrupt overflow enabled
       bsf         INTCON,GIE      ; Global interrupt enabled
 
       banksel     PORTB           ; Bank containing register PORTB
       clrf        PORTB           ; Clears port B
loop
       goto        loop            ; Remain here
       end                         ; End of program

Example 5

Using subroutine, using push-buttons

In the previous examples the microcontroller executes the program without being influenced in any way its surrounding. In practice, devices operating in this way are very rare (for example, simple neon signs). You guess, among other components, input pins will also be used in this example. There is a schematic in the figure below, while the program is on the next page. Everything is still very simple.

Example 5 - Using subroutine, using push-buttons

At the beginning of the program, immediately upon defining variables, the microcontroller pins* are configured by using registers TRISA and TRISB.

In the main program, one bit on port B is set first. Then the contents of this register is constantly moved by one place to the left (instruction rlf PORTB). It gives us the impression that the lit LED diodes is moving. To make it visible, the whole process must be slow enough. Press on the push-button "STOP" stops the movement and the program remains in loop3. Delay is provided by means of a nested loop. This time, it is placed in a short subroutine "DELAY".

* It is not necessary for PORTA pins since they are automatically configured as inputs after every reset.

Example 5:
;****************************************************************************
;                    Header
;****************************************************************************
;************* DEFINING VARIABLES *******************************************

       cblock      0x20            ; Block of variables starts at address 20h
       counter1                    ; Variable "counter1" at address 20h
       counter2                    ; Variable "counter2" at address 21h
       endc                        ; Block of variables ends
       
;************************ MAIN PROGRAM **************************************

       org         0x0000          ; Address of the first program instruction
       banksel     ANSEL           ; Selects bank containing register ANSEL
       clrf        ANSEL           ; Clears registers ANSEL and ANSELH to
       clrf        ANSELH          ; configure all inputs as digital

       banksel     TRISB           ; Selects bank containing register TRISB
       clrf        TRISB           ; All port B pins are configured as outputs
       movlw       B'00000010'
       movwf       TRISA           ; Pin RA1 is input
       
       banksel     PORTB           ; Selects bank containing register TRISB
       movlw       B'00000001'     ; Writes 1 to register W
       movwf       PORTB           ; Number is moved to PORTB
loop
       rlf         PORTB           ; Port B bits rotates by one place left
       call        DELAY           ; Calls subroutine "DELAY"
loop3
       btfss       PORTA,1         ; Tests the firs port A bit
       goto        loop3           ; "0" is applied to pin.Go to label "loop3"
       goto        loop            ; "1" is applied to pin.Go to label "loop"
       
;************************ SUBROUTINES ***************************************
DELAY
       clrf        counter2        ; Clears variable "counter2"
loop1
       clrf        counter1        ; Clears variable "counter1"
loop2
       decfsz      counter1        ; Decrements variable "counter1" by 1
       goto        loop2           ; Result is not 0. Go to label loop2
       decfsz      counter2        ; Decrements variable "counter2" by 1
       goto        loop1           ; Result is not 0. Go to lab loop1
       return                      ; Return from subroutine "DELAY"

       end                         ; End of program

EXAMPLE 6

TMR0 as a counter, defining new variables, using relay

This time, TMR0 is used as a counter. The idea is to connect the counter input to one pushbutton so that it counts one pulse at a time upon every button press. When the number of counted pulses becomes equal to the number in register TEST, logic one voltage (5V) will be applied to the PORTD, 3 pin. Since this voltage activates an electro-mechanical relay, this bit is called the same- "Relay".

In this example, the TEST register contains number 5. Naturally, it could be any number and could be calculated or entered via the keyboard. Instead of a relay, the microcontroller can activate some other device and instead of push-buttons it can use sensors. This example illustrates one of the most common uses of the microcontroller in industry. When something is done as many times as needed, then something else should be switched on or off...

Example 6 - TMR0 as a counter, defining new variables, using relay
Example 6:
;****************************************************************************
;                    Header                                            
;****************************************************************************
;************* DEFINING VARIABLES *******************************************

       TEST        equ B'00000101' ; Binary number 00000101 = TEST
       #define     RELAY PORTD,3   ; Pin PORTD,3 = RELAY
       
;************************ MAIN PROGRAM **************************************

       org         0x0000          ; Address of the first program instruction

       banksel     TRISB           ; Selects bank containing register TRISB
       clrf        TRISB           ; All port B pins are configured as outputs
       clrf        TRISD           ; All port D pins are configured as outputs
       movlw       B'00010000'     ; This number is written to W register
       movwf       TRISA           ; Only the forth pin of port A is input
       
       banksel     OPTION_REG      ; Bank containing OPTION_REG register
       bsf         OPTION_REG,T0CS ; Pin RA4 is supplied with pulses
       bsf         OPTION_REG,PSA  ; Prescaler rate is 1:1
       
       banksel     PORTB           ; Selects bank containing PORTB register
       
       clrf        TMR0            ; Clears timer register
       bcf         PORTD,3         ; Pin PORTD,3 = 0
loop
       movfw       TMR0            ; Timer register is moved to W register
       movwf       PORTB           ; W register is moved to PORTB
       xorlw       TEST            ; Operation exclusive OR between
                                   ; W register and number TEST (00000101)
       btfsc       STATUS,Z        ; If numbers are equal, result is 0 and
       bsf         PORTD,3         ; bit STATUS,Z = 1. Bit PORTD,3 is set
       goto        loop            ; and jump to label loop is executed
       
       end                         ; End of program

EXAMPLE 7

Using macros in the program, using debounce routine

You have probably noticed in the previous example that the microcontroller does not always operate as expected. Namely, by pressing the push-button, the number on port B is not always incremented by 1. Mechanical push-buttons make several short successive contacts when they have been activated. You guess, the microcontroller registers and counts all that...

Example 7 - Using macros in a program, using debounce routine

There are several ways to solve this problem. This program uses program delay known as debounce. Basically, it is a simple procedure. Upon input change detection (button press), a short program delay is provided and the program waits for another change (button release). Only after this, the program comes to a conclusion that the button is activated.

In this very case, the push-button is tested by means of macro called button. Besides, this macro contains a program delay which is provided by means of another macro pausems.

The main program is relatively simple and enables the variable "cnt" to be incremented and decremented by using two push-buttons. This variable is thereafter copied to port B and affects the LED (logic one (1) turns LED diode on, while logic zero (0) turns LED diode off).

Example 7:
;********************** Header **********************
;************* DEFINING VARIABLES *************************

       cblock      0x20            ; Block of variables starts at address 20hex
       
       HIcnt
       LOcnt
       LOOPcnt
       cnt
       endc                        ; End of block of variables
;**********************************************************************
       ORG         0x000           ; Reset vector
       nop
       goto        main            ; Go to program start (label "main")
;**********************************************************************

       include    "pause.inc"
       include    "button.inc"

;**********************************************************************
main
       banksel      ANSEL          ; Selects bank containing ANSEL
       clrf         ANSEL          ; All pins are digital
       clrf         ANSELH
 
       banksel      TRISB
       bsf          TRISA, 0
       bsf          TRISA, 1
       clrf         TRISB
       
       banksel      PORTB
       clrf         cnt       
Loop
       button       PORT,0,0,Increment
       button       PORT,1,0,Decrement
       goto         Loop
       
Increment
       incf         cnt, f
       movf         cnt, w
       movwf        PORTB
       goto         Loop
Decrement
       decf         cnt, f
       movf         cnt, w
       movwf        PORTB
       goto         Loop
       end                         ; End of program
Macro "pausems"
;**********************************************************************
pausems MACRO arg1
       local        Loop1
       local        dechi
       local        Delay1ms
       local        Loop2
       local        End

       movlw        High(arg1)     ; Higher byte of argument is moved
                                   ; to HIcnt
       movwf        HIcnt
       movlw        Low(arg1)      ; Lower byte of argument is moved
                                   ; to LOcnt
       movwf        LOcnt
Loop1
       movf         LOcnt, f       ; Decrements HIcnt and LOcnt while
       btfsc        STATUS, Z      ; needed and calls subroutine Delay1ms
       
       goto         dechi
       call         Delay1ms
       decf         LOcnt, f
       goto         Loop1
dechi
       movf         HIcnt, f
       btfsc        STATUS, Z
       goto         End
       call         Delay1ms
       decf         HIcnt, f
       decf         LOcnt, f
       goto         Loop1
Delay1ms:                          ; Delay1ms provides delay of
       movlw        .100           ; 100*10us=1ms
       movwf        LOOPcnt        ; LOOPcnt<-100
Loop2:
       nop
       nop
       nop
       nop
       nop
       nop
       nop
       decfsz      LOOPcnt, f
       goto        Loop2           ; Execution time of Loop2
       return                      ; is 10 us
End
       ENDM
;**********************************************************************
Macro "button"
;****************************************************
button MACRO port,pin,hilo,label
       local       Pressed1        ; All labels are local
       local       Pressed2
       local       Exit1
       local       Exit2
       
       IFNDEF      debouncedelay   ; Enables debounce time to be defined
                                   ; in main program
       #define     debouncedelay .10
       ENDIF
       
       IF (hilo == 0)              ; If pull-up used
       btfsc       port, pin       ; If "1", push-button is pressed
       goto        Exit1
       pausems     debouncedelay   ; Wait for 10ms debounce
Pressed1
       btfss       port, pin
       goto        Pressed1
       pausems     debouncedelay   ; Wait until released and
       goto        label           ; jump to specified address
Exit1
       ELSE                        ; If pull-down used
       btfss       port, pin
       goto        Exit2           ; If "0", push-button is released
       pausems     debouncedelay   ; Wait for 10ms debounce
Pressed2
       btfsc       port, pin
       goto        Pressed2
       pausems     debouncedelay   ; Wait until released and
       goto        label           ; jump to specified address
Exit2
       ENDIF
       
       ENDM
;**********************************************************************

EXAMPLE 8

Using timer TMR1 and using interrupt

16-bit timer TMR1 is used in this example. By occupying its registers TMR1L and TMR1H, an interrupt occurs and the number on port B is incremented. This has already been seen in the previous examples. The difference is in the program delay which is a bit longer this time because the prescaler rate is 1:8.

Example 8 - Using timer TMR1 and using interrupt
Example 8:
;*************************** Header *****************************************
;************* DEFINING VARIABLES *******************************************

       cblock      0x20            ; Block of variables starts at address 20h
       w_temp                      ; Variable at address 20h
       pclath_temp                 ; Variable at address 21h
       status_temp                 ; Variable at address 22h
       endc
       
;************************ PROGRAM START ************************
       org         0x0000          ; Address of the first program instruction
       goto        main            ; Jump to label "main"

;************************ INTERRUPT ROUTINE **********************************

       org         0x0004          ; Interrupt vector
       movwf       w_temp          ; Save register W

       movf        STATUS          ; Save register STATUS
       movwf       status_temp
       
       movf        PCLATH          ; Save register PCLATH
       movwf       pclath_temp
       
       banksel     PORTB           ; Selects bank containing PORTB
       incf        PORTB           ; Register PORTB is incremented by 1
       
       movf        pclath_temp,w   ; PCLATH is given its original content
       movwf       PCLATH
       movf        status_temp,w   ; STATUS is given its original content
       movwf       STATUS
       swapf       w_temp,f        ; W is given its original content
       swapf       w_temp,w
  
       banksel     PIR1            ; Selects bank containing PIR1
       bcf         PIR1,TMR1IF     ; Clears interrupt flag TMR1IF
       
       bsf         INTCON,GIE      ; Global interrupt enabled
       retfie                      ; Return from interrupt routine
       
;************************ MAIN PROGRAM ***************************************

main                               ; Start of main program
       banksel     ANSEL           ; Selects bank containing register ANSEL
       clrf        ANSEL           ; Clears registers ANSEL and ANSELH
       clrf        ANSELH          ; All pins are digital
       
       banksel     TRISB           ; Selects bank containing register TRISB
       clrf        TRISB           ; All port B pins are configured as outputs
       
       banksel     T1CON           ; Selects bank containing register T1CON
       bcf         T1CON,TMR1CS    ; TMR1 counts pulses generated by oscillator
       
       bsf         T1CON,T1CKPS0   ; Prescaler rate is 1:8
       bsf         T1CON,T1CKPS1
       bsf         T1CON,TMR1ON    ; Turns on timer TMR1
       
       banksel     PIE1            ; Selects bank containing register PIE1
       bsf         PIE1,TMR1IE     ; TMR1 interrupt overflow enabled
       bsf         INTCON,PEIE     ; Peripheral modules interrupt enabled       
                                   ; Timer TMR1 belongs to peripheral modules
       bsf         INTCON,GIE      ; Global interrupt enabled
       
       banksel     PORTB           ; Selects bank containing register PORTB
       clrf        PORTB           ; Clears port B
loop
       goto        loop            ; Remain here
       end                         ; End of program

EXAMPLE 9

Using timer TMR2, configuring quartz oscillator

This example illustrates the use of timer TMR2. The microcontroller uses internal oscillator HFINTOSC with the frequency of 500 kHz. The whole program works as follows: After the period of time defined by register PR, prescaler and postscaler has expired, an interrupt occurs. Interrupt routine decrements the content of the PR register and simultaneously increments the content of port B. Since the number in register PR, which determines when interrupt is to occur is constantly decremented, interrupt will occur for shorter and shorter periods of time. In other words, counting will be carried out faster. A new cycle of accelerated counting starts after every register PR overflow.

Example 9:
;********************** Header ***************************************************
;************* DEFINING VARIABLES ************************************************

       cblock      0x20            ; Block of variables starts at address 20h
       w_temp                      ; Variable at address 20h
       pclath_temp                 ; Variable at address 21h
       status_temp                 ; Variable at address 22h
       endc                                
       
;************************ PROGRAM START ******************************************
       org         0x0000          ; Address of the first program instruction
       goto        main            ; Jump to label "main"

;************************ INTERRUPT ROUTINE **************************************
 
       org         0x0004          ; Interrupt vector
       movwf       w_temp          ; Save register W
       
       movf        STATUS          ; Save register STATUS
       movwf       status_temp
       
       movf        PCLATH          ; Save register PCLATH
       movwf       pclath_temp
       
       banksel     PORTB           ; Selects bank containing PORTB
       incf        PORTB           ; Increments PORTB register by 1
       banksel     PR2             ; Selects bank containing PR2
       decf        PR2             ; PR2 is decremented by 1
       movf        pclath_temp,w   ; PCLATH is given its original state
       movwf       PCLATH
       movf        status_temp,w   ; STATUS is given its original state
       movwf       STATUS
       swapf       w_temp,f        ; W is given its original state
       swapf       w_temp,w
       
       banksel     PIR1            ; Selects bank containing PIR1
       bcf         PIR1,TMR2IF     ; Clears interrupt flag TMR2IF
       
       bsf         INTCON,GIE      ; Global interrupt enabled
       retfie                      ; Return from interrupt routine
       
;************************ MAIN PROGRAM *******************************************
main                               ; Start of the main program
       banksel     OSCCON          ; Selects bank containing register OSCCON
       bcf         OSCCON,6        ; Selects internal oscillator HFINTOSC with
       bsf         OSCCON,5        ; frequency of 500KHz
       bsf         OSCCON,4
       bsf         OSCCON,0        ; Microcontroller uses internal oscillator
       
       banksel     ANSEL           ; Selects bank containing register ANSEL
       clrf        ANSEL           ; Clears registers ANSEL and ANSELH
       clrf        ANSELH          ; All pins are digital
       
       banksel     TRISB           ; Selects bank containing register TRISB
       clrf        TRISB           ; All port B pins are configured as outputs
       clrf        PR2                            
       
       banksel     T2CON           ; Selects bank containing register T2CON
       movlw       H'FF'           ; Sets all control register bits
       movwf       T2CON           ; prescaler=1:16, postscaler=1:16 TMR2=ON
       clrf        PORTB
       
       banksel     PIE1            ; Selects bank containing register PIE1
       bsf         PIE1,TMR2IE     ; TMR2 interrupt enabled
       bsf         NTCON,PEIE      ; Peripheral modules interrupt enabled
                                   ; Timer TMR2 belongs to peripheral modules
       bsf         INTCON,GIE      ; Global interrupt enabled
loop
       goto        loop            ; Remain here
       end                         ; End of program

EXAMPLE 10

Module CCP1 as PWM signal generator

Since the CCP modules have a wide range of possibilities they are commonly used in practice. This example illustrates the use of CCP1 module in PWM mode. Bits of the CCP1CON register determine that the module operates as a single-output PWM. The same bits determine the PWM frequency to be 4.88 kHz. To make things more interesting, the duration of the output P1A (PORTC,2) pulses may be changed by means of push-buttons symbolically called "DARK" and "BRIGHT". Push-buttons are tested in interrupt routine initiated by the timer TMR1. Any change affects the LED diode so that it changes light intensity. Note that port B does not use external resistors because internal pull-up resistors are enabled. The whole process of generating PWM signal is performed "behind the scenes", which enables the microcontroller to do other things.

Example 10 - Module CCP1 as PWM signal generator
Example 10:
;********************** Header **********************************************
;************* DEFINING VARIABLES *******************************************
      cblock       0x20            ; Block of variables starts at address 20h
      w_temp                       ; Variable at address 20h
      pclath_temp                  ; Variable at address 21h
      status_temp                  ; Variable at address 22h
      endc
      
      #define      DARK PORTB,0    ; Push-button "DARK" is connected
                                   ; to PORTB,0 pin
      #define      BRIGHT PORTB,1  ; Push-button "BRIGHT" is connected
                                   ; to PORTB,1 pin
;************************ PROGRAM START *************************************

      org          0x0000          ; First program instruction address
      goto         main            ; Jump to label "main"

;************************ INTERRUPT ROUTINE *********************************

      org          0x0004          ; Interrupt vector
      
      movwf        w_temp          ; Save register W
      
      movf         STATUS          ; Save register STATUS
      movwf        status_temp
      
      movf         PCLATH          ; Save register PCLATH
      movwf        pclath_temp
      
      banksel      CCPR1L
      btfss        DARK            ; Tests push-button "DARK"
      decf         CCPR1L          ; Push-button is pressed - decrement CCP1L by 1
      btfss        BRIGHT          ; Testing push-button "BRIGHT"
      incf         CCPR1L          ; Push-button is pressed - increment CCP1L by 1
      
      movf         pclath_temp,w   ; PCLATH is given its original content
      movwf        PCLATH
      movf         status_temp,w   ; STATUS is given its original content
      movwf        STATUS
      swapf        w_temp,f        ; W is given its original content
      swapf        w_temp,w
      
      banksel      PIR1            ; Selects bank containing PIR1
      bcf          PIR1,TMR1IF     ; Clears interrupt flag TMR1IF
      
      bsf          TMR1H,7         ; Accelerates timer TMR0 counting
      bsf          TMR1H,6         ;
      bsf          INTCON,GIE      ; Global interrupt enabled
      retfie                       ; Return from interrupt routine
      
;************************ MAIN PROGRAM **************************************

main                               ; Start of the main program
      banksel      ANSEL           ; Selects bank containing register ANSEL
      clrf         ANSEL           ; Clears registers ANSEL and ANSELH
      clrf         ANSELH          ; All pins are digital
      
      banksel      OPTION_REG      ; Selects bank containing register ANSEL
      bcf          OPTION_REG,7    ; Pull-up resistors enabled      
      bsf          WPUB,0          ; Pull-up resistors enabled
      bsf          WPUB,1          ; on port B pins 0 and 1
      
      banksel      TRISC           ; Selects bank containing register TRISC
      clrf         TRISC           ; All port C pins are configured as outputs
      
      banksel       T1CON          ; Selects bank containing register T1CON
      bcf           T1CON,TMR1CS   ; TMR1 operates as a timer
      bcf           T1CON,T1CKPS0  ; Prescaler rate is 1:8
      bcf           T1CON,T1CKPS1
      bsf           T1CON,TMR1ON   ; Activates timer TMR1
      
      banksel       PIE1           ; Selects bank containing register PIE1
      bsf           PIE1,TMR1IE    ; Interrupt TMR1 is enabled
      bsf           INTCON,PEIE    ; Peripheral modules interrupts are
                                   ; enabled
      bsf           INTCON,GIE     ; Global interrupt enabled
      
      movlw         B'11111101'    ; Prescaler TMR2 = 1:4
      banksel       T2CON
      movwf         T2CON
      movlw         B'11111111'    ; Number in register PR2
      banksel       PR2
      movwf         PR2
      
      banksel       CCP1CON
      movlw         B'00001100'    ; Bits to configure CCP1 module
      movwf         CCP1CON
loop
      goto          loop           ; Remain here
      end                          ; End of program

EXAMPLE 11

Using A/D converter

PIC16F887 A/D converter is used in this example. Everything is quite simple. A variable analog signal is applied on the AN2 pin while the result of conversion is shown on port B as a binary number. In order to simplify the program as much as possible, only 8 lower bits of the result of conversion are shown. GND is used as a negative voltage reference Vref-, while positive voltage reference is applied on the AN3 pin. It enables voltage measurement scale to "stretch and shrink".

To make this clear, the A/D converter always generates a 10-bit binary result, which means that it detects a total of 1024 voltage levels (210=1024). The difference between two voltage levels is not always the same. The less the difference between Vref+ and Vref, the less the difference will be between two of 1024 levels. Accordingly, the A/D converter is able to detect slight changes in voltage.

Example 11 - Using A/D converter
Example 11:
;*********************** Header *********************************************
;************************ PROGRAM START *************************************

       org         0x0000          ; Address of the first program instruction

       banksel     TRISB           ; Selects bank containing register TRISB
       clrf        TRISB           ; All port B pins are configured as outputs
       movlw       B'00001100'
       movwf       TRISA           ; Pins RA2 and RA3 are configured as inputs
       
       banksel     ANSEL           ; Selects bank containing register ANSEL
       movlw       B'00001100'     ; Inputs AN2 and AN3 are analog while
       movwf       ANSEL           ; all other pins are digital
       clrf        ANSELH
       
       banksel     ADCON1          ; Selects bank including register ADCON1
       bsf         ADCON1,ADFM     ; Right justification of result
       bcf         ADCON1,VCFG1    ; Voltage Vss is used as Vref
       bsf         ADCON1,VCFG0    ; RA3 pin voltage is used as Vref+
       
       banksel     ADCON0          ; Selects bank containing register ADCON0
       movlw       B'00001001'     ; AD converter uses clock Fosc/2, AD channel
       movwf       ADCON0          ; on RA2 pin is used for conversion and
                                   ; AD converter is enabled
loop
       banksel     ADCON0
       btfsc       ADCON0,1        ; Tests bit GO/DONE
       goto        loop            ; Conversion in progress, remain in
                                   ; loop
       banksel     ADRESL
       movf        ADRESL,w        ; Lower byte of conversion result is
                                   ; copied to W
       banksel     PORTB
       movwf       PORTB           ; Byte is copied to PORTB
       bsf         ADCON0,1        ; Starts new conversion
       goto        loop            ; Jump to label "loop"
       end                         ; End of program

EXAMPLE 12

Using EEPROM memory

This example demonstrates write to and read from built-in EEPROM memory. The program works as follows. The main loop constantly reads EEPROM memory location at address 5 (decimal). This number is displayed on port D. The same loop tests the state of three push-buttons connected to port A. The push-buttons "INCREMENT" and "DECREMENT" have the same purpose like in example 7 - increment and decrement the variable "cnt" which is thereafter displayed on port B. The push-button "MEMO" enables that variable to be written to EEPROM memory. In order to check it, it is enough to press this push-button and switch off the device. On the next switch on, the program displays the value of the variable on port D (at the moment of writing, this value was displayed on port B).

Example 12 - Using EEPROM memory
Example 12:
;********************** Header **********************************************
;*********** Defining variables in program **********************************
       cblock      0x20            ; Block of variables starts at address 20h
       
       HIcnt
       LOcnt
       LOOPcnt
       cnt
       endc                        ; End of block
       
;****************************************************************************
       ORG         0x000           ; Reset vector
       nop
       goto        main            ; Go to start of the program (label "main")
;****************************************************************************
       include     "pause.inc"
       include     "button.inc"
;****************************************************************************
main
       banksel     ANSEL           ; Selects bank containing ANSEL
       clrf        ANSEL
       clrf        ANSELH          ; All pins are digital
       
       banksel     TRISB
       bsf         TRISA, 0        ; Input pin
       bsf         TRISA, 1        ; Input pin       
       bsf         TRISA, 2        ; Input pin
       clrf        TRISB           ; All port B pins are outputs
       clrf        TRISD           ; All port D pins are outputs
       banksel     PORTB
       clrf        PORTB           ; PORTB=0
       clrf        PORTD           ; PORTD=0
       clrf        cnt             ; cnt=0
Loop
       banksel     PORTA
       button      PORTA,0,0,Increment
       button      PORTA,1,0,Decrement
       button      PORTA,2,0,Save
       
       banksel     EEADR
       movlw       .5              ; Reads EEPROM memory location
       movwf       EEADR           ; at address 5
       banksel     EECON1
       bcf         EECON1,EEPGD
       bsf         EECON1,RD       ; Reads data from EEPROM memory
       banksel     EEDATA
       movfw       EEDATA          ; Moves data to W
       banksel     PORTD
       movwf       PORTD           ; Data is moved from W to PORTD
       goto        Loop
Increment                          ; Increments number on port B
       incf        cnt, f
       movf        cnt, w
       movwf       PORTB
       goto        Loop
Decrement                          ; Decrements number on port B
       decf        cnt, f
       movf        cnt, w
       movwf       PORTB
       goto        Loop
Save                               ; Copies data from port B to EEPROM
       banksel     EEADR           ; memory location at address 5
       movlw       .5
       movwf       EEADR           ; Writes address
       banksel     PORTB
       movfw       PORTB           ; Copies port B to register W
       banksel     EEDAT
       movwf       EEDAT           ; Writes data to temporary register
       banksel     EECON1
       bcf         EECON1,EEPGD
       bsf         EECON1,WREN     ; Write enabled
       
       bcf         INTCON,GIE      ; All interrupts disabled
       btfsc       INTCON,GIE
       goto        $-2
       
       movlw       55h
       movwf       EECON2
       movlw       H'AA'
       movwf       EECON2
       bsf         EECON1,WR

       btfsc       EECON1,WR       ; Wait for write to complete
       goto        $-1
       
       bsf         INTCON,GIE      ; Interrupt enabled
       bcf         EECON1,WREN
       goto        Loop            ; Tests push-buttons again
       end                         ; End of program

EXAMPLE 13

Two-digit LED counter, multiplexing

In this example, the microcontroller operates as a two-digit counter. Concretely, the variable Dval is decremented (slow enough to be visible) and its value is displayed on twodigit LED display (99-0). The challenge is to enable binary number to be converted in decimal one and split it in two digits (tens and ones). Besides, since the LED display segments are connected in parallel, it is necessary to ensure that they change fast in order to make impression of simultaneous light emission (time-division multiplexing). Remember that in electronics, multiplexing allows several analog signals to be processed by one analog-todigital converter (ADC). In this very case, time-division multiplexing is performed by the timer TMR0, while binary to decimal number conversion is performed in macro "digbyte". Counter may be reset to its starting value (99) at any moment by pressing the pushbutton "COUNTER RESET".

Example 13 - Two-digit LED counter, multiplexing
Example 13:
;************************* Header *******************************************
;****************************************************************************
;                   DEFINING VARIABLES IN PROGRAM
       w_temp      EQU 0x7D        ; Variable for saving W register
       
       status_temp EQU 0x7E        ; Variable for saving STATUS register
       
       pclath_temp EQU 0x7F        ; Variable for saving PCLATH register
       
       CBLOCK      0x20            ; Block of variables starts at address 20h
       
       Digtemp
       Dig0                        ; Variables for displaying digits - LSB
       Dig1
       Dig2
       Dig3                        ; Variables for displaying digits - MSB
       Dval                        ; Counter value
       One                         ; Auxiliary variable which determines which
                                   ; display is to be switched on
                                   
       ENDC                        ; End of block of variables
       
       poc_vr      EQU .99         ; Initial  counter value is 99

       include     "Digbyte.inc"

;****************************************************************************
       ORG         0x0000          ; First instruction address
       goto        main            ; Jump to label "main"
;****************************************************************************
       ORG         0x0004          ; Interrupt vector address
       
       movwf       w_temp          ; Move w register to w_temp register
       
       
       movf        STATUS,w        ; Move STATUS register to status_temp
       movwf       status_temp     ; register
       
       movf        PCLATH,w        ; Move PCLATH register to pclath_temp
       movwf       pclath_temp     ; register
       
       ; Start of interrupt routine...

       BANKSEL     TMR0
       movlw       .100
       movwf       TMR0
       bcf         INTCON, T0IF
       
       bcf         PORTA, 0
       bcf         PORTA, 1
       btfsc       One, 0
       goto        Lsdon
       goto        Msdon
       
Lsdon
       incf        One, f
       movlw       HIGH (Bcdto7seg)
       movwf       PCLATH
       digbyte     Dval
       movf        Dig1, w
       call        Bcdto7seg       ; Place L1 mask on the PORTD
       movwf       PORTD
       bsf         PORTA, 1
       goto        ISR_end
       
Msdon
       incf        One, f
       movlw       HIGH (Bcdto7seg)
       movwf       PCLATH
       digbyte     Dval
       movf        Dig0, w
       call        Bcdto7seg       ; Place LO mask on the PORTD
       movwf       PORTD
       bsf         PORTA, 0
       goto        ISR_end
       
       ; End of interrupt routine...

ISR_end
       movf        pclath_temp,w   ; PCLATH register is given its original
       movwf       PCLATH          ; state
       
       movf        status_temp,w   ; STATUS register is given its original
       movwf       STATUS          ; state
       
       swapf       w_temp,f        ; W register is given its original
                                   ; state
       swapf       w_temp,w
       retfie                      ; Return from interrupt routine
main
       banksel     ANSEL           ; Selects bank containing ANSEL
       clrf        ANSEL           ; All pins are digital
       clrf        ANSELH

       BANKSEL     TRISA
       movlw       b'11111100'     ; RA0 and RA1 are configured as outputs and
                                   ; used for 7-segment display multiplexing
                                   ; RA2 is input push-button for initializa
                                   ; tion
                                   
       movwf       TRISA
       clrf        TRISD
       
       BANKSEL     OPTION_REG
       movlw       b'10000110'     ; TMR0 is incremented each 32us (Fclk=8MHz)
       movwf       OPTION_REG
       
       BANKSEL     PORTA
       movlw       poc_vr
       movwf       Dval            ; Dval contains counter value
       movlw       b'00000001'     ; Initializes variable specifying display
       movwf       One             ; to switch on
       movwf       PORTA
       movlw       .100
       movwf       TMR0            ; TMR0 interrupt appr.every 10ms
       bsf         INTCON, GIE     ; Global interrupt enabled
       bsf         INTCON, T0IE    ; Timer TMR0 interrupt enabled
       bcf         INTCON, T0IF
Loop
       btfss       One, 3          ; Falling edge encountered?
       goto Dec                    ; Yes! Go to Dec
       btfss       PORTA, 2        ; Counter reset button pressed?
       goto        Reset           ; Yes! Go to Reset
       goto        Loop
       ; Decrement Dval counter by 1
Dec
       btfss       One, 3
       goto        Dec
       movf        Dval, f
       btfsc       STATUS, Z       ; Is Dval equal to 0?
       goto        Loop            ; If it is, go to loop and wait for T2
       decf        Dval, f         ; If Dval not equal to 0, decrement it by 1
       goto        Loop
Reset
       btfss       PORTA, 2        ; Wait for rising edge
       goto        Reset
       movlw       poc_vr
       movwf       Dval            ; Write initial value to counter
       goto        Loop
;****************************************************************************
       ORG         0x0300          ; Lookup table is at the top of third page, but
                                   ; can be placed at some other place, it is impor
                                   ; tant to have it all on one page
Bcdto7seg
       addwf       PCL, f
       DT          0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f
;****************************************************************************
       END         ; End of program
Macro "digbyte":
digbyte MACRO arg0
       LOCAL       Exit0
       LOCAL       Exit1
       LOCAL       Exit2
       
       clrf        Dig0
       clrf        Dig1
       clrf        Dig2          
       clrf        Dig3
       
       movf        arg0, w
       movwf       Digtemp
       movlw       .100
Exit2
       incf        Dig2, f
       subwf       Digtemp, f
       btfsc       STATUS, C
       goto        Exit2
       decf        Dig2, f
       addwf       Digtemp, f
Exit1
       movlw       .10           
       incf        Dig1, f
       subwf       Digtemp, f
       btfsc       STATUS, C
       goto        Exit1
       decf        Dig1, f
       addwf       Digtemp, f
Exit0
       movf        Digtemp, w
       movwf       Dig0
       ENDM

Macro digbyte is used to convert the number from digital to decimal format. Besides, digits of such decimal number are stored into special registers in order to enable them to be displayed on LED displays.

EXAMPLE 14

Sound generating, using macros

The generation of sound is a task commonly assigned to the microcontroller. Basically, it all comes to generating a pulse sequence on one output pin. While doing so, the proportion of logic zero (0) to logic one (1) duration determines the tone pitch and by changing different tones, different melodies arise.

In this example, any press on push-buttons T1 and T2 generates a sound. The appropriate instructions are stored in macro "beep" containing two arguments.

Macro Beep
Example 14 - Sound generating, using macros
Example 14:
;*********************** Header ***************************************
;******************* Defining variables in program ********************
       cblock      0x20
       HIcnt                       ; Auxiliary variables for macro pausems
       LOcnt
       LOOPcnt
       PRESCwait
       Beep_TEMP1                  ; Belongs to macro "BEEP"
       Beep_TEMP2
       Beep_TEMP3
       endc
       
#define  BEEPport PORTD, 2         ; Speaker pin
#define  BEEPtris TRISD, 2

       expand

;**********************************************************************
       ORG         0x0000          ; RESET vector address
       goto        main            ; Jump to program start (label - main)
;**********************************************************************
; remaining code goes here

       include     "pause.inc"
       include     "button.inc"
       include     "beep.inc"
main
       banksel     ANSEL           ; Selects bank containing ANSEL
       clrf        ANSEL           ; All outputs are digital
       clrf        ANSELH
       
       banksel     TRISD
       movlw       b'11111011'     ; PORTA D initialization
       movwf       TRISD
       banksel     PORTD
       BEEPinit                    ; Macro "Beep"
Loop
       button      PORTD,0,0,Play1 ; Push-button 1
       button      PORTD,1,0,Play2 ; Push-button 2
       goto        Loop
       
Play1                              ; First tone
       BEEP        0xFF, 0x02
       BEEP        0x90, 0x05
       BEEP        0xC0, 0x03
       BEEP        0xFF, 0x03
       goto        Loop
       
Play2                              ; Second tone
       BEEP        0xBB, 0x02
       BEEP        0x87, 0x05
       BEEP        0xA2, 0x03     
       BEEP        0x98, 0x03
       goto        Loop
;**********************************************************************
       END                         ; End of program
Macro "beep":
BEEPinit    MACRO
       bcf         STATUS, RP0
       bcf         STATUS, RP1
       bcf         BEEPport
       bsf         STATUS, RP0
       bcf         STATUS, RP1
       bcf         BEEPtris
       movlw       b'00000111'     ; TMR0 prescaler rate 1:256
       movwf       OPTION_REG      ; OPTION <- W
       bcf         STATUS, RP0
       bcf         STATUS, RP1
       ENDM
BEEP   MACRO       freq, duration
       bcf         STATUS, RP0
       bcf         STATUS, RP1
       movlw       freq
       movwf       Beep_TEMP1
       movlw       duration
       movwf       Beep_TEMP2
       call        BEEPsub
       ENDM
;**********************************************************************
; Subroutines

BEEPsub
       clrf        TMR0            ; Counter initialization
       bcf         INTCON, T0IF
       bcf         BEEPport
BEEPa
       bcf         INTCON, T0IF    ; Clears TMR0 Overflow Flag
BEEPb
       bsf         BEEPport
       call        B_Wait          ; Logic one "1" duration
       bcf         BEEPport
       call        B_Wait          ; Logic zero "0" duration
       btfss       INTCON, T0IF    ; Check TMR0 Overflow Flag,
       goto        BEEPb           ; skip next if set
       decfsz      Beep_TEMP2, f   ; Is Beep_TEMP2 = 0 ?
       goto BEEPa                  ; Go to BEEPa again
       return
B_Wait
       movf Beep_TEMP1, w
       movwf Beep_TEMP3
B_Waita
       decfsz Beep_TEMP3, f
       goto B_Waita
       return

EXAMPLE 15

Using LCD display

This example illustrates the use of the alphanumeric LCD display. The program itself is very simple because macros are used (usually the effort of creating Macros pays off in the end).

Two messages written on two lines change on display. The second message is intended to display the current temperature. Since no sensor is installed, the measurement is not really carried out, the variable "temp" appears on the display instead of the measured temperature.

In reality, the current temperature or some other measured value would be displayed.

Example 15 - Using LCD display
Example 15:
;*********************** Header ***********************************
                  ; DEFINING VARIABLES IN PROGRAM
                  
       CBLOCK      0x20            ; Block of variables starts at address 20h
       
       HIcnt                       ; Belongs to macro "pausems"
       LOcnt
       LOOPcnt
       
       LCDbuf                      ; Belongs to functions "LCDxxx"
       LCDtemp
       LCDportBuf                  ; LCD Port Buffer
       
       Digtemp                     ; Belongs to macro "digbyte"
       Dig0
       Dig1
       Dig2
       Dig3
       
       temp
       ENDC                        ; End of block
       
LCDport   EQU PORTB   ; LCD is on PORTB (4 data lines on RB0-RB3)
RS        EQU 4       ; RS line connected to RB4
EN        EQU 5       ; EN line connected to RB5

;**********************************************************************
       ORG         0x0000          ; Reset vector address
       nop
       goto        main            ; Go to beginning of the program (label "main")
;**********************************************************************
       include    "LCD.inc"
       include    "digbyte.inc"
       include    "pause.inc"
;**********************************************************************
main
       banksel     ANSEL           ; Selects bank containing ANSEL
       clrf        ANSEL           ; All pins are digital
       clrf        ANSELH
       
       bcf         STATUS,RP0      ; Bank0 active only
       bcf         STATUS,RP1
       movlw       .23
       movwf       temp            ; Move arbitrary value to variable
                                   ; is to be displayed on LCD
       lcdinit                     ; LCD initialization
Loop
       lcdcmd      0x01            ; Instruction to clear LCD
       lcdtext     1, "mikroelektronika" ; Write text from the begin
                                           ; ning of the first line
       lcdtext     2, "Beograd"  ; Write text from the beginning of
                                   ; the second line
       pausems     .2000           ; 2 sec. delay
       lcdcmd      0x01            ; Instruction to clear LCD
       lcdtext 1, "Temperatura1" ; Write text from the begin
                                   ; ning of the first line
       lcdtext 2, "temp=" ; Write text from the beginning of
                            ; the second line
       lcdbyte     temp            ; Write variable (dec.)
       lcdtext 0, " C"           ; Write text after cursor
       pausems     .2000           ; 2 sec. delay
       goto        Loop
;**********************************************************************
       end                         ; End of program
LCD.inc
;**********************************************************************
; Initialization must be done by using macro lcdinit before access
; ing LCD
;**********************************************************************
lcdinit    MACRO
       bcf         STATUS, RP0     ; Bank0
       bcf         STATUS, RP1
       clrf        LCDportBuf
       movf        LCDportBuf, w
       movwf       LCDport
       bsf         STATUS, RP0     ; Bank1
       bcf         STATUS, RP1
       clrf        TRISB           ; LCDport with output LCD
       bcf         STATUS, RP0     ; Bank0
       bcf         STATUS, RP1
       
; Function set (4-bit mode change)
       movlw       b'00100000'
       movwf       LCDbuf
       swapf       LCDbuf, w
       movwf       LCDportBuf
       bcf         LCDportBuf, RS
       movf        LCDportBuf, w
       movwf       LCDport
       bsf         LCDportBuf, EN
       movf        LCDportBuf, w
       movwf       LCDport
       bcf         LCDportBuf, EN
       movf        LCDportBuf, w
       movwf       LCDport
       call        Delay1ms        ; 1 ms delay
       
; Function set (display mode set)
       lcdcmd      b'00101100'
       call        Delay1ms        ; 1 ms delay
       
; Display ON/OFF Control
       lcdcmd      b'00001100'
       call        Delay1ms        ; 1 ms delay
       
; Entry Mode Set
       lcdcmd      b'00000110'
       call        Delay1ms        ; 1 ms delay
       
; Display Clear
       lcdcmd      b'00000001'
       pausems     .40             ; 40 ms delay
       
; Function set (4-bit mode change)
       movlw       b'00100000'
       movwf       LCDbuf
       swapf       LCDbuf, w
       movwf       LCDportBuf
       bcf         LCDportBuf, RS
       movf        LCDportBuf, w
       movwf       LCDport
       bsf         LCDportBuf, EN
       movf        LCDportBuf, w
       movwf       LCDport
       bcf         LCDportBuf, EN
       movf        LCDportBuf, w
       movwf       LCDport
       call        Delay1ms        ; 1 ms delay
       
; Function set (display mode set)
       lcdcmd      b'00101100'
       call        Delay1ms        ; 1 ms delay
       
; Display ON/OFF Control
       lcdcmd      b'00001100'
       call        Delay1ms        ; 1 ms delay
       
; Entry Mode Set
       lcdcmd      b'00000110'
       call        Delay1ms        ; 1 ms delay
       
; Display Clear
       lcdcmd      b'00000001'
       pausems     .40             ; 40 ms delay
       
       ENDM

;**********************************************************************
; lcdcmd sends command to LCD (see the table on the previous page)
; lcdclr is the same as lcdcmd 0x01
;**********************************************************************
lcdcmd MACRO LCDcommand         ; Send command to LCD
       movlw       LCDcommand
       call        LCDcomd
       ENDM
       
LCDcomd
       movwf       LCDbuf
       bcf         LCDportBuf, RS
       movf        LCDportBuf, w
       movwf       LCDport
       goto        LCDwr
LCDdata
       movwf       LCDbuf
       bsf         LCDportBuf, RS
       movf        LCDportBuf, w
       movwf       LCDport
       goto        LCDwr
LCDwr
       swapf       LCDbuf, w
       call        SendW
       movf        LCDbuf, w
       call        SendW
       return
SendW
       andlw       0x0F
       movwf       LCDtemp

       movlw       0xF0
       andwf       LCDportBuf, f
       movf        LCDtemp, w
       iorwf       LCDportBuf, f
       movf        LCDportBuf, w
       movwf       LCDport
       call        Delay1ms
       bsf         LCDportBuf, EN
       movf        LCDportBuf, w
       movwf       LCDport
       bcf         LCDportBuf, EN
       movf        LCDportBuf, w
       movwf       LCDport
       call        Delay1ms
       return
       
;**********************************************************************
; lcdtext writes text containing 16 characters which represents a
; macro argument. The first argument select selects the line in which
; text writing is to start. If select is 0, text writing starts from
; cursor current position.
;**********************************************************************
lcdtext    MACRO select, text      ; This macro writes text from cursor
                                   ; current position. Text is specified
                                   ; in argument consisting of 16 charac
                                   ; ters
       local       Message
       local       Start
       local       Exit
       local       i=0
       goto        Start
       Message     DT text         ; Create lookup table from arguments
       DT          0
Start
       IF (select == 1)
       lcdcmd b'10000000'
       ELSE
       IF (select == 2)
       lcdcmd b'11000000'
       ENDIF
       ENDIF
       
       WHILE (i<16)                ; Repeat conditional program compiling 16 times
       call Message+i              ; Read lookup table and place value in W
       addlw 0
       bz Exit                     ; until 0 is read
       call LCDdata                ; Call routine displaying W on LCD
       i=i+1
       ENDW
Exit
       ENDM
       
;**********************************************************************
; This macro writes value in size of 1 byte on LCD
; excluding leading zeros
;**********************************************************************
lcdbyte    MACRO arg0
       digbyte     arg0            ; A hundred is in Dig2,
                                   ; A ten is in Dig1 and one in Dig0
       movf        Dig2, w
       addlw       0x30
       call        LCDdata
       movf        Dig1, w         ; If digit is 0 move cursor
       addlw       0x30
       call        LCDdata
       movf        Dig0, w         ; If digit is 0 move cursor
       addlw       0x30
       call        LCDdata
       ENDM
;**********************************************************************
; 1ms Delay
Delay1ms:
       movlw       .200
       movwf       LOOPcnt
Delay10us:
       nop                         ;1us
       nop                         ;1us
       nop                         ;1us
       nop                         ;1us
       nop                         ;1us
       nop                         ;1us
       nop                         ;1us
       decfsz      LOOPcnt, f      ;1us
       goto        Delay10us       ;2us
       
       return

EXAMPLE 16

RS232 serial communication

This example illustrates the use of the microcontroller's EUSART module. Connection to the PC is enabled through RS232 standard. The program works in the following way: Every byte received via the serial communication is displayed using LED diodes connected to port B and is automatically returned to the transmitter thereafter. If an error occurs on receive, it will be signalled by switching the LED diode on. The easiest way to test device operation in practice is by using a standard Windows program called Hyper Terminal.

Example 16 - RS232 serial communication
Example 16:
;*********************** Header ***************************************
;                  DEFINING VARIABLES IN PROGRAM

       w_temp      EQU 0x7D        ; Variable for saving W register
       status_temp EQU 0x7E        ; Variable for saving STATUS register
       pclath_temp EQU 0x7F        ; Variable for saving PCLATH w register

       cblock      0x20            ; Block of variables starts at address 20 h
       Port_A                      ; Variable at address 20 h
       Port_B                      ; Variable at address 21 h
       RS232temp                   ; Variable at address 22 h
       RXchr                       ; Variable at address 23 h
       endc                        ; End of block of variables
;**********************************************************************
       ORG         0x0000          ; Reset vector
       nop
       goto        main            ; Go to beginning of program (label "main")
;**********************************************************************
       ORG         0x0004          ; Interrupt vector address
       
       movwf       w_temp          ; Save value of W register
       movf        STATUS,w        ; Save value of STATUS register
       movwf       status_temp
       movf        PCLATH,w        ; Save value of PCLATH register
       movwf       pclath_temp
;**********************************************************************
; This part of the program is executed in interrupt routine
       banksel     PIE1
       btfss       PIE1, RCIE
       goto        ISR_Not_RX232int
       banksel     PIE1
       btfsc       PIR1, RCIF
       call        RX232_int_proc
       
ISR_Not_RX232int
       movf        pclath_temp,w
       movwf       PCLATH          ; PCLATH is given its original value
       
       movf        status_temp,w
       movwf       STATUS          ; STATUS is given its original value
       swapf       w_temp,f
       swapf       w_temp,w        ; W is given its original value
       
       retfie                      ; Return from interrupt routine
;**********************************************************************
RX232_int_proc                     ; Check if error has occurred
       banksel     RCSTA
       movf        RCSTA, w
       movwf       RS232temp
       btfsc       RS232temp, FERR
       goto        RX232_int_proc_FERR
       btfsc       RS232temp, OERR
       goto        RX232_int_proc_OERR
       goto        RX232_int_proc_Cont
       
RX232_int_proc_FERR
       bcf         RCSTA, CREN     ; To clear FERR bit, receiver is first
                                   ; switched off and on afterwards
       nop                         ; Delay ...
       nop
       bsf         RCSTA, CREN
       movf        RCREG, w        ; Reads receive register and clears FERR bit
       bsf         Port_A, 0       ; Switches LED on ( UART error indicator)
       movf        Port_A, w
       movwf       PORTA
       goto        RS232_exit
       
RX232_int_proc_OERR
       bcf         RCSTA, CREN     ; Clears OERR bit
       nop                         ; Delay ...
       nop
       bsf         RCSTA, CREN
       movf        RCREG, w        ; Reads receive register and clears FERR bit
       bsf         Port_A, 1       ; Switches LED on ( UART error indicator)
       movf        Port_A, w
       movwf       PORTA
       goto        RS232_exit
       
RX232_int_proc_Cont
       movf        RCREG, W        ; Reads received data
       movwf       RXchr
       movwf       PORTB
       movwf       TXREG           ; Sends data back to PC
       
RS232_exit
       return                      ; Return from interrupt routine
;**********************************************************************
; Main program

main
       banksel     ANSEL           ; Selects bank containing ANSEL
       clrf        ANSEL           ; All inputs are digital
       clrf        ANSELH
       
       ;---------------------------------------
       ; Port configuration
       ;---------------------------------------
       banksel     TRISA
       movlw       b'11111100'
       movwf       TRISA
       movlw       b'00000000'
       movwf       TRISB
       ;---------------------------------------
       ; Setting initial values
       ;---------------------------------------
       banksel     PORTA
       movlw       b'11111100'
       movwf       PORTA
       movwf       Port_A
       movlw       b'00000000'
       movwf       PORTB
       movwf       Port_B
       ;---------------------------------------
       ; USART - setting for 38400 bps
       ;---------------------------------------
       banksel     TRISC
       bcf         TRISC, 6        ; RC6/TX/CK = output
       bsf         TRISC, 7        ; RC7/RX/DT = input
       
       banksel     BAUDCTL       
       bsf         BAUDCTL, BRG16
       banksel     SPBRG
       movlw       .51             ; baud rate = 38400
                                   ; ( Fosc/(4*(SPBRG+1)) ) Error +0.16%
       movwf       SPBRG
       clrf        SPBRGH
       
       banksel     TXSTA
       bcf         TXSTA, TX9      ; Data is 8-bit wide
       bsf         TXSTA, TXEN     ; Data transmission enabled
       bcf         TXSTA, SYNC     ; Asynchronous mode
       bsf         TXSTA, BRGH     ; High-speed Baud rate
       
       banksel     RCSTA
       bsf         RCSTA, SPEN     ; RX/DT and TX/CK outputs configuration
       bcf         RCSTA, RX9      ; Select mode for 8-bit data receive
       bsf         RCSTA, CREN     ; Receive data enabled
       bcf         RCSTA, ADDEN    ; No address detection, ninth bit may be
                                   ; used as parity bit
       movf        RCSTA, W
       movf        RCREG, W
       ;---------------------------------------
       ; Interrupts enabled
       ;---------------------------------------
       banksel     PIE1
       bsf         PIE1, RCIE      ; USART Rx interrupt enabled
       
       bsf         INTCON, PEIE    ; All peripheral interrupts enabled
       bsf         INTCON, GIE     ; Global interrupt enabled
       
       ;---------------------------------------
       ; Remain here
       ;---------------------------------------
       goto $
       
       end                         ; End of program