Sie sind auf Seite 1von 192

C What Happens

USING PIC MICROCONTROLLERS AND THE CCS C COMPILER

DAVID BENSON
VERSION 1.0

NOTICE
The material presented in this book is for the education and amusement of students, hobbyists, technicians and engineers. Every effort has been made to assure the accuracy of this infor mation and its suitability for this purpose. Square 1 Electronics and the author assume no responsibility for the suitability of this information for any application nor do we assume any liability resulting from use of this information. No patent liability is assumed for use of the information contained herein. All rights reserved. No part of this manual shall be reproduced or transmitted by any means (including photo copying) without written permission from Square 1 Electronics and the author. Copyright 2008 David Benson

TRADEMARKS
Registered trademarks of Microchip Technology, Inc: PICmicro PIC PICSTART Plus PICkit 2 MPLAB ICD 2 ICSP In-Circuit Serial Programming Registered trademarks of Microsoft Corporation: Microsoft Windows Hyper Terminal Registered trademarks of Hilgraeve, Inc.: HyperTerminal Private Edition

PUBLISHER

Square 1 Electronics P.O. Box 1414 Hayden, ID 83835 U.S.A. Voice (208)664-4115 FAX (208)772-8236 EMAIL sqone@pacific.net http://www.sq-1.com

C What Happens
INTRODUCTION PIC MICROCONTROLLER PRODUCT OVERVIEW SELECTING A DEVICE FOR EXPERIMENTS PIC16F818 1 4 6 8

Pins and functions Package Clock oscillator Reset Ports Special Features PIC microcontroller architecture Code and data protection Configuration bits
CIRCUIT FOR PIC16F818 EXPERIMENTS CHOOSING DEVELOPMENT TOOLS

8 8 9 9 10 10 11 13 13
14 18

CCS compiler Device programming methods Device programmers and ease of running code examples Device programmer In-circuit serial programmer Choosing a device programmer Microchip PICSTART Plus Choosing an in-circuit programmer/debugger CCS ICD-U40 (or -S40) Microchip PICkit 2 Microchip ICD 2
PROGRAMMING A DEVICE USING THE ICD-U40 (or -S40) PROGRAMMING A DEVICE USING THE PICkit 2 PROGRAMMING A DEVICE USING THE ICD 2 PROGRAMMING A DEVICE USING THE PICSTART Plus CCS COMPILER

18 18 18 18 19 19 20 20 20 20
22 26 28 31 32

C SOURCE CODE

34

What it looks like Typing accuracy Comments Text And Formatting


BITS, BYTES, ETC.

34 34 34 34
36

Bit Nibble Byte Binary Hexadecimal


CONSTANTS VARIABLES DATA

36 36 36 36 38
40 41 42

Data types ASCII characters


NAMING CONSTANTS AND VARIABLES

43 44
45

Reserved words in C
OPERATORS - SHORT LIST TRUE vs. FALSE DEVICE FILES PRE-PROCESSOR DIRECTIVES - SHORT LIST INs AND OUTS OF DIGITAL I/O CONFIGURATION REGISTER(S) FUSES FUNCTIONS

45
47 48 48 49 51 53 54

main() function Functions Built-in functions - short list


STATEMENTS

55 56 57
58

Executable statements Blocks Conditional statements Semicolon use rules

58 58 58 59

PROGRAM DESIGN

60

Program design - control flow if if/else while loop do/while loop for loop switch/case break continue return goto Rule Modular programming
WRITING PROGRAMS (With Experiments)

61 61 62 64 65 66 67 67 68 68 68 68 69
70

Programming concepts 70 Programming examples 72 Simple data transfers 75 Loop - endless 76 While loop 77 Do/while loop 78 Port registers accessed as variables 79 Port addresses defined using 79 #byte directives Port addresses defined using 80 user-created include file Port addresses defined using 81 get environment built-in function Loop with a counter 82 For loop 82 Loop until 84 While loop 84 Comparisons 86 Relational operators 86 If/else 86 Switch/case 87 Function calls and time delays 89 Bit-level I/O using built-in functions 92 Bit toggle 93 If statement - read switch position 94 ! logical operator 96 && logical operator (two switches) 97 logical operator (two switches) 97 if/else, else, else 98 Read input bit, write output bit 100 Event counting 102

Bit manipulation using bit manipulation functions Bit set/clear Bit testing Flags #bit pre-processor directive example typedef example Bit manipulation using bitwise operators Shift bits right or left Change specific bit to "1" Change specific bit to "0" Change specific bit to its compliment Goto Function library Cut and paste
TALKING TO A PIC MICROCONTROLLER WITH A PC VIA A WINDOWS TERMINAL PROGRAM

104 104 104 105 106 106 107 107 108 108 108 110 112 112
113

"U"-turn experiment PC-to-PC "2-lane highway" experiment PC/PIC microcontroller PC baud rates RS-232 interface for a PIC microcontroller PIC microcontroller-to-PC serial communication Formatting PIC microcontroller data on a PC screen
STRINGS ARRAYS

114 117 118 118 119 121 122


125 127

Index to an array Step through array elements Extract n**1 element from array Add offset to index Lookup tables 7-segment LED display
STRUCTURES

127 128 129 130 131 131


133

Structures and ports - bit fields


MATH AND MANIPULATING NUMBERS

137
140

Mathematical operators 140 Operator precedence 140 Data type selection considerations 142 Formatting variables such as math results for printing 143
PASSING VARIABLES 146

Passing variables Returning variables Prototyping functions

146 147 147

OPERATORS

149

Assignment operator Relational operators Logical operators Increment and decrement Mathematical operators Bitwise operators Pointer operators Structure operators Operators that don't fit the categories
INTERRUPTS

149 149 149 149 150 150 150 150 150


151

External interrupt sources 152 Internal interrupt sources 152 Timer 0 interrupt 152 Port B interrupt on change - bits 7, 6,5,4 152 Interrupts generated by other peripherals 153 Global interrupt enable flag (GIE) 153 Return from interrupt 153 Where to put the interrupt service routine in program 153 memory Interrupt latency 153 Multiple external interrupt sources 153 Interrupts in C 154 Functions - Built-in 154 Pre-processor directives used to identify 154 interrupt service routines Example - external interrupt 155
TIMING AND COUNTING USING TIMER 0 158

Digital output waveforms Using timer 0 Prescaler Putting timer 0 to work Setting up timer 0 Starting timer 0 Counter How do we know timer 0 is doing something? Timer 0 will keep on counting as long as: Timer 0 must be reloaded after each overflow for repeating time intervals Stopping timer 0 Timer 0 experiments Digital output waveform using timer 0 - internal clock Single time interval - internal clock Free running mode - internal clock - 0.1 second period Single time interval - external clock Free running mode - internal clock Counting events (pulses) Going further

158 159 160 161

162 162 163 166 169 170 174 175

ANALOG TO DIGITAL CONVERSION INSERTING ASSEMBLY CODE IN C CODE APPENDIX A - PULSER APPENDIX B - SOURCES APPENDIX C - HEXADECIMAL NUMBERS APPENDIX D - PROGRAM LISTINGS vs. PAGE NUMBERS

176 179 180 181 182 183

INTRODUCTION
The internal operation of a microcontroller is all about reading and writing to registers, or some times a bit in a register. This is done to: Control the operation of the microcontroller itself. To communicate with the outside world via input or output pins (lines). To move data from one register to another. To perform mathematical calculations. And more.

Assembly language programmers select and use instructions from the microcontroller's instruc tion set and put them in the proper order to make the desired (hopefully) things happen. C does a lot of this byte and bit level stuff for you. C is a high level language. You, the pro grammer, create the overall plan in the form of C source code. The C compiler generates an assembly level program and the file containing the l's and 0's that get programmed into the microcontroller (device) itself. C does not have an instruction set. Functions and executable statements get the job done. Some functions are built-in to the compiler, and some you write yourself. The C compiler that we will be using has a lot of built-in functions specific to PIC microcontrollers which will make your job much easier. You will write the executable statements. You will find that there are lots of ways to do the same thing in C (that work). I have two goals which conflict. The most important one is to give you the basics in the simplest, cleanest, most consistent wray possible to minimize confusion and move you toward writing your own pro grams (that work) as soon as possible. The secondary goal is to show' you enough about other ways to do things that you will be able to understand other people's code, especially the exam ples and drivers that come with the compiler which have been created by various authors over time. Reading this book will not give you an encyclopedic knowledge of C. It is not meant to be erudite. It is meant to be informal and user friendly. My goal is to help you be successful. No matter what programming language or specific device you use, you will need to know some thing about the internal workings/layout of the device you choose to work w'ith and the electrical connections to the outside world. In this book, we will use the PIC 16F818 for running the examples. The information that you need to understand the examples is provided so you won't have to look elsew'here for it (w ith the exception of some specific details listed in the CCS C Compiler Reference Manual). When you move on to projects of your ow n using other devices, you will need a copy of the Microchip data sheet for each device. It will be book-length and available for download at microchip.com in PDF format.

The PIC16F818, like the majority of PIC microcontrollers, has program memory made using flash technology, which means it can be erased electrically. It can be programmed, tested in-cir cuit and reprogrammed if necessary in a matter of a few minutes and without the need for an ultraviolet (UV) EPROM eraser. It is a small device (18-pins), readily available to all including hobbyists and students at a cost of $4.00 (at this writing) in single quantity. Think of the PIC16F818 as a custom I/O handler. It looks at its inputs and, based on what it sees, it sends signals out its outputs. You can customize it to do what you w'ant via program ming. It is not a heavy duty number cruncher or data manipulator. A variety of device programmers arc available for the PIC16F818 from independent program mer manufacturers for as little as $40. Learning how PIC microcontrollers work and how to apply them involves study in three areas: Use of a computer running Microsoft Window's (tm) (as needed). C programming language and C compiler. PIC microcontroller itself. The use of "pow'er tools" for programming PIC microcontrollers is essential. This means learn ing to use an IBM compatible computer if you haven't already done so. It also means learning to use a C compiler which converts English-like readable instructions into machine language understood by the PIC microcontroller itself. Finally, learning about the PIC microcontroller's inner workings is possible once use of the power tools is understood. The usual approach used by others to teach the use of PIC microcontrollers has been to get into all of the theory and details of the programming language and then show advanced examples using one of the more complicated parts. As usual, only 5 percent of this information is needed to get started, but which 5 percent. The approach taken here will be to give you the 5 percent you need to get going using the P1C16F818 masquerading as a relatively simple part. The object is to make this process as easy and enjoyable as possible. Once you get through this and you have programmed a PIC16F818 for the first time, a wrhole newr world awaits. You will be able to create more interesting projects and have more fun!

The assumption is made that you know how' to do the following on a computer running Microsoft Window's: Create a new folder. Copy part of the contents of a CD to the folder. Use a simple text editor to create a text file, save it, make a copy of it, print it, and copy it to the folder. As a beginner, you need to type the code examples in this book yourself, make the typing mis takes we all make, and learn what the error messages generated by C compiler mean. That is why the code in this book is not on our web site. The code for our intermediate and advanced level books is on our web site.

GENERAL INFORMATION
See our web site at http://www.sq-l.com for updates and for errata.

Let's C what happens!

PIC MICROCONTROLLER PRODUCT OVERVIEW


This book is not about one device. It is about the whole Microchip microcontroller product line. We need a place to start, a simple (relatively speaking) device which will be available for an extended period. I feel that the best device for initial learning purposes is the PIC16F818. What you learn in the process of using it is applicable, in varying degrees, to the entire Microchip product line. In a simplistic way, Microchip's 8-bit microcontroller line may be classified or categorized in three groups as follows: 12-bit core base-line - 10, 12 and 16 series part numbers 14-bit core mid-range - 12 and 16 series part numbers 16-bit core - 18 series part numbers A different set of rules applies to each group. The number of bits in the instruction words corre spond to the core width (in bits). We don't really care how' many bits are in an instruction word or how' wide the core is. We merely need to know what category a given device belongs in so we know' which set of rules apply to it. All of these microcontrollers arc classified as 8-bit devices because the data and data bus are 8 bits wide. The most popular segment of the 8-bit product line is the 14-bit core mid-range parts. Our atten tion will be focused there. The 12-bit core base-line parts are less sophisticated than the 14-bit core parts and are a step backwards (dumbed down). They still have plenty of capability for many low-end applications and are widely used where production volume is high and unit cost must be very low. They are not used much by experimenters. The 18 series parts have many similarities with the mid-range devices, but require a separate dis cussion. This group is growing. Microchip has introduced a new' 16-bit microcontroller family. The data and data bus are 16 bits w ide. The core is 24 bits wide. These devices have a lot of computation capability and on board peripherals. Once you learn the fundamentals of using PIC microcontrollers, there is plen ty of room to grow. So, the information in this book applies directly to the 14-bit core parts.

Chances are that when you choose a part for a project or product, it won't be the one you begin your learning experience with. Learn with the PIC16F818 and branch out later to learn about other devices that interest you. You will need an PIC16F818 data sheet (really a book) as a ref erence as well as a data sheet for each device you become interested in later on. This informa tion is available on Microchip's web site. Microchip has product family information available on their web site. As I am writing this, you can select 8-bit PIC Microcontrollers, then PIC16MCU to arrive at a matrix of information con sisting of devices going down and features going across the matrix. Their web site is constantly changing, so you may have to poke around a little to get there. Compare the features and layout of devices of interest with the soon to be familiar PIC16F818 as a reference point.

SELECTING A DEVICE FOR EXPERIMENTS


This book is not about one or two specific PIC microcontrollers. The information presented will serve as a foundation for working with all Microchip microcontrollers. In order to do experi ments and to create code that works, we must select a specific device to work with. By making minor changes, the code examples in this book will run on many other PIC microcontrollers in the 14-bit core product line. One of the great things about C is that code can be ported from one device to another easily. I have chosen the PIC16F818 is the example because: It has 18 pins (small). It has an internal clock oscillator with 8 speeds selectable via software. The 8 speed clock oscillator is a relatively new design used in many new devices currently being introduced by Microchip. It has the best mix of on-board peripherals (timer/counters, A/D converter) for beginner and intermediate level experimentation. It has in-circuit debugging capability built into the device which you will find is a big advantage as you move forward to more complex projects. There are lots of features and their associated registers inside the PIC16F818 which I will keep hidden from you so you don't have to worry about them until some time down the road (next book). The PIC 16F818 will appear to you as a simple device with a few pins which will go unused for now. When choosing a dcvice for an application, one would look at factors such as program memory size, on-board peripherals such as A/D, timer/counters, etc.

When firing up a device you have not used before for the first time, you must do the following: Determine whether or not there are analog peripherals (A/D and/or comparators) which must be turned off if not used. The CCS C compiler will do this for you as a default. If there is a multi-speed internal clock oscillator (software selectable speed), you must determine what speed it will run at when the device is powered-up and whether or not the speed must be changed during initialization of the device to suit your application. Determine what features are controlled by the configuration word(s) as the device is programmed by the device programmer and what selections should be made. Using C will greatly simplify this process for you. Determine how many configuration registers there are and how to write to them. Using C will greatly simplify this process for you. Explanations of these things follow as appropriate. I have made the choices for you when using the PIC16F818 as your example for the experiments. However, I will show you how to do this on your own.

PIC16F818
PINS AND FUNCTIONS

The PIC 16F818 is fabricated using CMOS technology. It consumes very little power and is fully static meaning that the clock can be stopped and all register contents will be retained. The maximum sink or source current for a port at any given time is as follows:

Any I/O Pin Port A Port B Sink current Source current 2 5 mA 2 5 mA 100 raA 100 mA 100 mA 100 mA

Supply current is primarily a function of operating voltage, frequency and I/O pin loading and is typically 2 mA or so for a 4MHz clock oscillator and 5 volts. This drops to less than 100 microamps (even a few microamps) in the power-managed modes (see data sheet). Because the device is CMOS, all inputs must go somewhere. All unused inputs should be pulled up to the supply voltage (usually I 5 VDC) via a 10 K resistor.

PACKAGE
The PIC16F818 is available in an 18-pin DIP package suitable for the experimenter. The current part number is PIC 16F818-I/P.

CLOCK OSCILLATOR
The internal clock oscillator may be used (most common), as is done in this book, or an external clock oscillator may be used. The details of various external oscillator circuits and components as well as internal clock oscillator use options are given in the Microchip data sheet. At pro gramming time, the part must be told via configuration bits which clock oscillator option will be used. This will be explained as we go along. For experimentation, we will use the internal clock oscillator as follows: Default frequency (31.25 KFIz) for most applications. 4 MHz for applications using a time delay (built-in function or timer 0).

RESET
The PIC16F818 has built-in power-on reset which works as long as the power supply voltage comes up quickly. Commonly the MCLR pin is merely tied to the power supply using a pull-up resistor. A switch may be used to regain control if things run away.

For our experiments, we will use pin 4 as MCLR which stands for Master Clear (reset). It will be pulled up to +5volts via a 47 K resistor to keep the device out of reset unless the MCLR pin is pulled low by some external device.

If you choose to use one of the ICD-type in-circuit serial programmers, the programmer will use pin 4 for the programming threshold voltage, Vpp, which puts the device in programming mode. After programming is completed, you may bring the device out of reset to test your program using the ICD control interface running on the PC. This makes programming and testing your codc fast and easy.

PORTS
Port A, as we will be using it, has 7 bits/lines. Port B is 8 bits/lines wide or byte-wide. Each port line may be individually programmed as an input line or output line. This is done using a special function which matches a bit pattern with the port lines in registers callcd "tristate" or "tris" registers. A "0" associated with a port line makes it an output, a "1" makes it an input. Examples follow. Pins which may have analog functions in use are analog functions when the microcontroller comes out of reset. For the PIC16F818, the CCS compiler will generate an instruction which changes those pins to digital I/O as part of the initialization process unless the compiler encoun ters a call to setup_adc_ports ( ) ; . This is a default and it is very important to keep in mind. The Port B lines have weak pullup resistors on them which may be enabled or disabled under software control. All 8 resistors are enabled/disabled as a group via a built-in function in the compiler (not used in this book). The pullup resistor on an individual port line is automatically turned off when that line is configured as an output. The pullups are disabled on power-on reset. Port A, bit 4 is shared with the external timer/counter input called TOCKI. As a digital input line, the input is Schmitt trigger. As a digital output line, it is open drain, so a pullup resistor is required. The output cannot source current, it can only sink current. For experimenting, all unused port lines should be tied to the power supply via 10 K pullup resistors (CMOS rule - all inputs must go somewhere). On reset, all port lines are inputs.

SPECIAL FEATURES Watchdog Timer


The watchdog timer is useful in some control applications where a runaway program could cause a safety problem. We will not deal with it exccpt to say that it is important to select "watchdog timer off' when programming the configuration bits.

Power-up Timer (PWRT)


The power7up timer holds the device in reset for a time which allows the power to come up to a level wjiich will provide reliable operation of the device at which time it is allowed to come out of resetV The power-up timer should be selected "enabled" when programming the configuration bits.

Brown-out Reset (BOR)


If Vdd falls below approximately 4 volts for about 100 microseconds, the device will be reset. The brown-out reset feature should be selected "enabled" when programming the configuration bits.

Sleep Mode
The feature of the "sleep mode" is drastically reduced power consumption achieved by turning off the main clock oscillator.

10

In-Circuit Debugging
The PIC16F818 is designed so that an in-circuit debugger may be connected to it (advanced topic).

Low-Voltage ICSP Programming


Low voltage ICSP will not be used.

Peripherals
Peripherals such as timer/counters and A/D converters will be discussed in chapters devoted to the subjects.

Special Feature Selection


Details follow.

PIC MICROCONTROLLER ARCHITECTURE


PIC microcontrollers have two separate blocks of memory, program memory and data memory.

Program Memory
The PIC16F818 program memory is 14 bits wide and 1K words long. Program memory is flash which means it can be erased electrically. Program memory is read-only at run time for the purposes of this book. PIC microcontrollers can only execute code contained in program memory.

Pointed To By Reset Vector

Pointed To By Interrupt Vector

0 x 3 F F _______________________

A limited amount of data may be stored in program memory (see Writing Programs chapter).

11

Weird Hex Notation


The "Ox" means hexadecimal. The Ox notation comes from the C programming language. The main thing is, when you see OxOF, it means hexadecimal OF. 0x004 means hexadecimal 004. Some of the newer Microchip literature uses "h" to denote hexadecimal numbers. 3FFh means 3FF hexadecimal. h'3FF' has the same meaning.

Data Memory
Data memory consists of register files containing registers of two types: General purpose registers which hold your data. Special Function Registers (SFRs). The register files are 8 bits wide (with the exception of the PCLATH register which is 5 bits wide). The P1C16F818 has a 512 register file address space (0x000 - Ox IFF) divided into four banks, but not all addresses are used.
Register File 0x00 01
02

03 04 05 06 07 08 09 0A 0B 0C

Indirect Address TMR0 PCL Status File Select Port A Data Port B Data

Indirect Address Pointer * Timer/Counter Program Counter Low Order 8 Bits Status Register - Flags Indirect Pointer Port A Port B

PCLATH INTCON

Program Counter Latch High Order 5 Bits Interrupt Control

IF
20

General Purpose Registers Think. Of This Area As RAM (Data Memory) 0x7F Bank 0 Not Physically Implemented Note: Bank Switching And Banks 2 And 3 Ignored

64 file registers have specific dedicated purposes and are called Special Function Registers (SFRs). For the most part, the C compiler knows what to do with the SFRs and the address of each. Wc will discuss the very few situations in which the compiler needs help finding address es as the need arises. 128 registers arc there for the C compiler to use and may be thought of as RAM or data memory for storing data during program execution.

12

Data EEPROM Memory


Data EEPROM memory is not directly mapped into the data memory register file address space described earlier. Instead, it is addressed using six special function registers and some built-in C functions. This topic is beyond the scope of this book. Oh yes, I neglected to spell out what the acronym means. It stands for memory that can be read, written to, or even erased electronically. It is usually used to store data acquired during program execution (think data logger).

CODE AND DATA PROTECTION


The code protection bits in the configuration register may be set so as to protect the code in pro gram memory, the data in data memory, and the data in EEPROM memory from examination by the outside world (so your code can't be ripped off). Your program can still access and change the contents of data EEPROM memory with the code protection bits set.

CONFIGURATION BITS
The configuration bits are loeated in flash memory outside the main part of flash memory used for program storage. They are used to determine things like clock oscillator type, functions of some of the pins, etc. There is a chapter devoted to this subject. The device programmer accesses these bits during the device programming procedure. By doing this, the device will be in the correct configuration when it comes out of reset.

13

CIRCUIT FOR EXPERIMENTS


One simple circuit may be used for all but one of the experiments in this book. The exception utilizes a 7-segment LED digital display.

TOCKI ANO INT RA4 RA1 RAO RBO

14

Looking at what is included may give you some ideas about how you would like to construct it. My suggestions on how to proceed follow. A simple circuit board may be assembled which includes a socket for a PIC16F818, power supply terminal block, power supply decoupling capacitors, three 3-pin headers and shorting blocks for use in selecting functions for pins RAO, RA1 and RA4, screw terminal blocks as a means of connecting RAO/ANO, RA I, RA4/T0CKI, and RBO/INT to the off-board components used in the experiments, and DIP switches for pins RAO, 1, 2, 3. A modular jack is included for easy connection to any of the three ICD/programmers described in the book.

If you decide to use a device programmer rather than use an ICD as a programmer, I would defi nitely use a ZIF socket for the PIC microcontroller to avoid bending or damaging the pins. 18-pin ZIF sockets are becoming expensive and difficult to find. The once common part made by the TEXTOOL division of 3M is still available from Digi-Key. It is the gold plated (literally) version (3M part number 218-3341-00-0602J) and the cost is around $18. A 24-pin Aries socket is available from JAMECO, Digi-Key and others. The Aries part number is 24-6554-10 (tin plated contacts) and the price will be in the $8 range. Simply ignore the extra 6 pins. Pullup resistors are used in the experiments primarily for the purpose of preventing unused inputs from floating. There are pullups on port B built into the PIC16F818. I decided not to use them because they just add confusion to the program examples and detract from explanation of the applications themselves. So........... as a beginner, when you use the circuit module, remember there are 10 K pullup resistors on all unused port lines. You can save refinements for later.

15

A modular phone jack is used to connect to the ICD via cable. A printed circuit board style jack is shown. One manufacturer is tyco Electronics AMP. The part number is 5204703. The Digi-Key part number is A9049-ND.

The modular phone jack is connected as shown:

Modular Jack 6-Conductor

tyco Electronics / AMP

Mfgr. P/N 520470-3 Digi-Key P/N A9049-ND

16

If you arc not able to find the small modular phone jack and you want one NOW, you may pur chase a wall-mount phone jack and whack it down to size using a saw. It will have screw termi nals on the back. Short wires may be used to connect it to your board or a solderless bread board.

The example in the photos has one end cut off to show the concept. The one that I have used for some time has all four sides of the mounting plate portion cut off.

CCS has a nice PIC16F818 board available which includes what I have described above plus a little more. See Appendix B - Sources.

CHOOSING DEVELOPMENT TOOLS


CCS C COMPILER
One of the great things about the CCS C compiler is that it includes tots of built-in functions (think subroutines if you are not familiar with C) which control the PIC microcontroller on board peripherals. You will not have to learn about or concern yourself with many of the inter nal workings (think control registers) of the microcontroller. If you want to read the A/D con verter, simply use the READ ADC() function. Precision time delay built-in functions are avail able for your use. CCS also supplies drivers to control popular external devices so you don't have to work so hard figuring out how to do it on your own. The CCS C compiler is a power tool for creating the code for your applications.

DEVICE PROGRAMMING METHODS Device Programmers And Ease Of Running Code Examples
There are two choices. lise a device programmer, program the device, remove it from the socket on the programmer, place it in the socket on the test circuit, power-up the test circuit, and run the program. This is easier than it sounds. Use an in-circuit debugger (ICD) as a programmer This is done using a method called in-circuit serial programming (tm) (ICSP) (tm) and the device is programmed in the experiment board. Immediately after programming, the circuit may be exercised by clicking on a "run" (or similarly named) button on-screen. Moving the device is not necessary. This is the easiest method. ICDs are the least expensive way to go these days unless you already own a device programmer.

Device Programmer
A device programmer is used to load code into a device and that's it. The device is usually clamped in the programmer's zero insertion force (ZIF) socket during programming. After pro gramming is completed, the device is removed from the ZIF socket and inserted in the socket on the experiment board. The board is powered-up and the code is tested.

In-Circuit Serial Programmer


An in-circuit debugger (ICD) may be used to download code into a device, run the code, and debug the code. Debugging is not covered in this book, however, a debugger may be used to load the example programs in this book into a device followed by bringing the device out of reset so the code will run. The device is part of the example circuit when the programming takes place, a process called in-circuit serial programming (ICSP).

18

In-circuit serial programming requires the use of two port lines, generally port B, pins 7 and 6. To keep things simple, the examples in this book do not use these two pins for the test circuit. This allows in-circuit programming followed immediately by running the program using the ICD on-screen controls to bring the microcontroller out of reset which allows the program to run. In an industrial/commercial product development environment, port B, pins 7 and 6 would be used in the application and special means (a special substitute device or a header containing one) would be used to gain access to the part for debugging purposes while leaving port B, pins 7 and 6 free. This is an advanced topic and wc won't concern ourselves with the details here. The PIC16F818 may be programmed using an ICD as an in-circuit serial programmer followed, immediately, by running the code. Available ICD's include the CCS ICD-U40 (or ICD-S40), the PICkit 2 (tm), and the Microchip ICD 2 (tm). Three advantages of using this method over using a device programmer (only) are: The convenience of running the code immediately following programming. The low cost of the tool. The ICD will be available for debugging when you move up to more advanced projects.

CHOOSING A DEVICE PROGRAMMER


Selecting a programmer (only, not an ICD) for PIC microcontroller development is a personal choicc based on the following criteria: Range of parts which can be programmed. Support (level, long-term). Price. Simple, inexpensive device programmers are available which run under Microsoft Windows. As product offerings are continually changing in this fast-paced market, I suggest you contact the programmer manufacturers directly for the latest information. Note that some programmers are connected to the PC's parallel (printer) port, some arc connected to a serial port (usually COM2), and some are USB devices.

Microchip PICSTART Plus (tm)


Microchip's PICSTART Plus will program their 5 volt dual in-line (DIP) packagc devices. The latest version of the PICSTART Plus contains a PIC 18 scries microcontroller which converts information received from the PC's serial port to the appropriate signals to program each device. Since these devices have differing requirements, the code in the PIC 18 microcontroller must be updated to cover the requirements of new devices as they become available. The latest code (firmware) may be downloaded from Microchip's web site at no charge. The PICSTART Plus operates under Windows and is connectcd to one of the PC's serial ports (usually COM2).

19

The control software for the PICSTART Plus is incorporated in MPLAB (tm) which is Microchip's development software. MPLAB is also updated frequently to incorporate support for new devices as they are introduced. The PICSTART Plus currently sells for about $200.

CHOOSING AN IN-CIRCUIT PROGRAMMER/DEBUGGER CCS ICD-U40 (or -S40)


The 1CD-U40 uses a USB interface and the ICD-U40 uses an RS-232 serial interface. They each use different control software. The CCS ICD will program the devices used as examples used in this book using ICSP and will allowr the program to run after the device is programmed. The ICD Control Software is separate from the compiler software. The ICD-U40 or -S40 currently costs approximately $75.

Microchip PICkit 2 (tm)


The PICkit 2 uses a USB interface. The PICkit 2 will program the devices used as examples used in this book using ICSP and will allow the program to run after the device is programmed. Control software comes with the PICkit 2 and will handle the tasks we need to perform with the examples in this book. The PICkit 2 may be used under the control of MPLAB for many devices. We will use the PICkit 2 control software in our examples. The PICkit 2 MCU programmer (PG164120) currently costs about $35. I suggest purchasing a RJ-11 to ICSP adapter (AC 164110) ($ 10) to go with it. It has a header at one end to interface with the PICkit 2 and a short 6-conductor cable with a modular plug on the other to interface w ith the modular jack on your board.

Microchip ICD 2 (tm)


The ICD 2 will also program the device used as an example in this book using ICSP and will allow the program to run after the device is programmed. MPLAB is used to control the ICD 2. An ICD 2 with a USB cable and powrer supply sells for approximately SI90.

20

Programming Software

Programming (only) Connections

CCS ICD Control Software

PICkit 2 Application Software (MPLAB for some devices)

MPLAB

PICSTART Plus

MPLAB

21

PROGRAMMING A DEVICE USING THE ICD-U40 (or -S40)

INSTALLING THE ICD CONTROL SOFTWARE


Follow the CCS directions on CD.

USING THE ICD AS A PROGRAMMER


To program a C object file (.cot) into a PIC microcontroller: Connect the 1CD-U40 to your PC using the USB cable included with the ICD module. Connect the ICD to your target board using the short modular phone cable (6-conductor) supplied with the ICD. The target board should be powered by your +5 volt logic power supply. Power-up the PC which will supply power to the ICD via the USB interface. Power-up the Target board. Open the ICD control software. Note that this program is separate from the compiler software.

22

The ICD Control Program window appears.

Targets Supported: All

To program and exercisc an example program, click on the Advanced button. The ICD Advanced window appears.

Our objectives are: Halt the device (hold the MCLR line low). _____ Run example programs (put the target device in run mode by allowing the MCLR line to be pulled high). Program (write) example programs into a device. We will not concern ourselves with debugging as that is an advanced topic. Halt vs. Run can be selected by clicking on the appropriate button in the Target State area in the ICD Advanced window. To program a dcvicc, a .cof file must have been created previously using the compiler.

24

With the ICD Advanced window open:


Halt the target device. In the Write Device area, click on From Hex/Cof file. The Download To Target window will appear. Navigate to the object file you wish to write to the target device and select it (will be highlighted when you have selected it). Click Open. This will initiate programming. In the message area in the lowrer left comer of the ICD Advanced window, a message should appear indicating that your source file has been written to the target device. Click on the Run button. There will be a slight delay. Observe your program running (look at LEDs or whatever depending on what the code is supposed to do). When you have finished your celebration (you did do everything correctly didn't you!), click Halt. You may repeat the programming process to test another program. When you are finished: Power-off the target board first (always!). Power-off the PC last. Disconnect the USB cablc. When using the CCS ICD to program a PIC microcontroller, the software also supports .hex files (not just .cof files). Since .hex files are much smaller and do not contain the C source, .hex files are more often used in a production setting.

PROGRAMMING A DEVICE USING THE PICkit 2


To program a .cof file into a PIC microcontroller: The PICkit 2 comes with its own programming software which we will use here. Some devices may be programmed using MPLAB and accessing the PICkit 2 as one of the programmer options under MPLAB. The PICkit 2 has a 6-pin header socket as the interface to a user board. Products are often designed with a 6-pin header on them to allow in-circuit serial programming (ICSP) after the product is assembled and just before it is shipped allowing the latest firmware version to be pro grammed into the product. Firmware may also be changed by the customer in the field after the product is in use. for experimenting and development, Microchip offers an adapter consisting of a very small board with a header to mate with the socket on the PICkit 2 plus a modular phone jack. A short 6-conductor phone cable is included to conncct the adapter to a modular phone jack on your board. The Microchip part number is AC 164110 (ICD 2 to ICSP adapter).

Connect the PICkit 2 , adapter, modular phone cable, and your board together. Conncct your board to your +5 volt logic power supply (power supply off). Connect the PICkit 2 to your PC using the USB cable supplied with it. Power-up the PC. Power-up your board. Open the PICkit 2 programming software.

26

The PICkit 2 software will come up recognizing the device on your board (assuming the devicc family you are using was selected the last time the software was used). If not, on the Menu Bar, select Devicc Family>Midrange. Check /MCLR in the small Vdd PICkit 2 box. Load your .cof file into MPLABs Program Memory (zone). File>Import Hex Navigate to your .cof file. Select the file. Click Open. A message will appear indicating that the file has been imported. Click on the Write button to program the device. A message should appear reporting success. Uncheck /MCLR in the small Vdd PICkit 2 box. Your program should run. To halt program execution check /MCLR. This will hold the device in reset. To program and run another program, load another .cof file and repeat the programming procedure. When you have finished experimenting, power-down using the reverse sequence: Power-down the target board. Power-down the PC.

27

PROGRAMMING A DEVICE USING THE ICD 2


DESCRIPTION
The Microchip in-circuit debugger (ICD 2) consists of software which runs on a PC (included in MPLAB) and hardware which looks like a colorful hockey puck (two of the colors are shown here).

For the purposes of this book, the ICD 2 will be used as an In-Circuit Serial Programmer (tm). The unit has a USB connector for serial communication with a host PC and a 6-conductor modu lar phone jack for communication with a flash PIC microcontroller. The PIC microcontroller resides on a so-called "target" board which is the user's (your) board. Microchip strongly recommends using USB and NOT the RS-232 connection which is built into the ICD 2 for communications between the host PC and the ICD 2. Following the current version of the Microchip USB Port Setup instructions is a MUST. Refer to the "Readme for MPLAB ICD 2" contained in the Readmes folder in the MPLAB folder. I recommend purchasing the ICD 2 full kit with power supply (DV164007) and storing the RS-232 serial cable somewhere.

28

The ICD 2 can operate in two operating modes: Debugger mode (not discussed here). Programmer mode. In the programmer mode, your code is programmed into the device for stand alone (without the ICD 2 connected) operation. The code can, however, be run with the ICD 2 connected. Selecting the best option for powering the ICD 2 and the target is critical. Based on my experi ence and that of others, I recommend powering the ICD 2 using Microchip's wall transformer power supply and NOT via the USB port. I also recommend powering the target (your circuit) with your own power supply.

USER BOARD = TARGET BOARD


For the purposes of this book, the PIC16F818 experiment board described earlier is set up to be used with the ICD 2. You can easily set up another board of your own in a similar way by pro viding the modular phone jack and using the 47 K pull-up resistor on MCLR (reset).

SETTING UP THE ICD 2


Connect the ICD 2 to your PIC16F818 board using a short 6-conductor modular phone cable. Connect your PIC 16F818 board to a suitable +5 volt DC power supply. When powering-up the ICD 2 and PIC16F818 board the first time or for use with a new project, use the following procedure: 1. At start-up, NO power should be applied to the PIC 16F818 board. 2. Power-up the host PC. 3. Power the ICD 2 . The green "Power" LED in the ICD 2 should be on. 4. Start MPLAB. At this point, it is assumed that you have a .cof file suitable to be programmed into the PIC16F818 on your board. 5. Configure>Select Devicc. Select PIC16F818. 6. Select ICD 2 as the programmer to be used. Programmer>Select Programmer>MPLAB ICD 2.

29

7. A Setup wizard will appear the first time the ICD 2 is connected. Select USB as the communications method. Select target has owrn power supply. Select auto connect to ICD 2. Select ICD 2 automatically downloads the required operating system. 8. Select Programmer>Settings. 9. The MPLAB ICD 2 Settings dialog box will open. Click the Power tab and ensure that the check box for "Power target circuit from ICD 2" is NOT checked. Click OK. This is important! 10. Power-up your PIC 16F818 board. 11. Open the output window. View>Output. 12. Select Programmer>Connect. 13. Observe the activity in the output window. On completion, the next to the last two text lines should read "Running ICD Self Test... Passed" and the last text line should read "MPLAB ICD 2 Ready". 14. You should now be able to debug and erase the PIC16F818 on your board. Reverse the procedure to power-down (PIC16F818 board off, ICD 2 off, PC off).

PROGRAMMING PROCEDURE
Soooooo........ once the setup has been done, use the following procedure to program a .cof file into a PIC microcontroller: 1. 2. 3. 4. 5. 6. 7. 8. Connect the ICD 2 to your board (target board). Connect the ICD 2 to your PC. Power-up the PC. Power-up the ICD 2 using the ICD 2 power supply. Power-up the target board using your own power supply. Open MPLAB. Configure>Select Device. Programmer>Select Programmer. Select MPLAB ICD 2. 9. Programmer>Connect (if you don't have auto-connect selected). 10. Import your .cof file into MPLAB's Program Memory (zone). File>Import. Navigate to your .cof file. Select the file. Click Open. A message will appear indicating that the file has been imported. 11. Programmer>Program. A message in the Output window should report success. 12. Programmer>Release from Reset to run your program. 13. Import another .cof file and repeat the procedure. 14. Programmer>Disable Programmer (assuming you are finished programming devices for a while). 15. Power-down using the reverse sequence: Power-down the target board. Power-down the ICD 2 by disconnecting the power cable. Power-down the PC.

30

PROGRAMMING A DEVICE USING THE PICSTART Plus

To program a .cof file into a PIC microcontroller: Connect the PICSTART Plus to your PC (as usual). Power-up the PICSTART Plus. Open MPLAB. Configure>Select Device. Programmcr>Se 1 ect Programmer. Select PICSTART Plus. Programmer>Enable Programmer. A message in the Output window will indicate your PICSTART Plus firmware version. Load your .cof file into MPLAB's Program Memory (zone). File>Import. Navigate to your .cof file. Select the file. Click Open. A message will appear indicating that the file has been imported. Insert the device in the ZIF socket. Be careful about pin orientation. Programmer>Program. A message in the Output window should report success. Remove the programmed device from the ZIF socket. Programmer>Disable Programmer (assuming you are finished programming devices for a while). Power-down the PICSTART Plus. NEVER power-up the PICSTART Plus with a device in it as the device may be damaged!

31

CCS COMPILER

INSTALLING CCS COMPILER - PCWH


Follow the CCS directions on the CD.

USING THE COMPILER


Create a new' folder for your C stuff. Create a source file. File>New>Source Csl The compiler will add .c file name extension. Type your first source code. Call it Csl as an example. Create a new project. Project>Create. The Select main source file window appears. Select device. Select your file (Csl or whatever). Click Open. The Project options window appears. Your file name has been selected automatically (if you had one open). Click Apply. Compile your code. Click on the Compile tab on the menu bar at the top of the screen. The Compile menu ribbon will appear. Click on the Compile icon on the Compile menu ribbon. Your source code will be compiled successfully, we hope. The Output window will appear indicating how the compile process turned out. For the examples in this book, a warning message will appear: >Warning 208 Csl.c Line 6 (i,5): Function not void and does not return a value main Ignore the warning. This is what we wanted, so celebrate!! A C object file (.cof file) was created as part of the process of compiling your source code (done in the background). It is located in the same folder as your source code (Csl .cof). This is the file that will be programmed into the PIC microcontroller.

32

If you have PIC microcontroller assembly language programming expcricnce, you may want to take a look at the assembly language listing of the code generated by the compiler. To do this, click on the Output files icon on the right end of the Compile menu ribbon. Click on the C/ASM List icon. : in proper places critical!! One missing semicolon can result in a lot of compiler error messages of various kinds. I am cer tain that you will never experience this.

C SOURCE CODE
This is what C source code looks like:
////first C program ala pictl.asm Csla #include <16F818.h> #fuses INTRC_IO,NOWDT,PUT,NOPROTECT,BROWNOUT,MCLR,NOLVP,NOCPD,NOWRT,NODEBUG #fuses CCPB2 //internal clock osc with I/O on RA7, RA6 //watchdog timer disabled power-up timer enabled //code protection off brown-out reset enabled //mclr enabled low-voltage programming disabled //no EE data protection write protection off //no debug CCPl function pin RB2 (arbitrary) main()

{
setuposcillator(0SC4MHZ); outputb (OxOf); while (TRUE); //4 MHz clock oscillator //bit pattern to port B //circle, always

} This is a very simple program which runs on a PIC16F818. We will assume that there are 8 LEDs connected to port B. Executing this program causes 4 LEDs to be off and 4 LEDs to be on. I am sure this program looks very cryptic to you now. When you have finished reading this book and doing the experiments, this program and much more complex ones will no longer seem cryptic. As you read the next few chapters, you can refer to this program to see how the topics relate to this program. For now, let's make some observations about the overall look of the program.

TYPING ACCURACY
Typing accuracy is very important when creating C source code. A punctuation mark, either typed by mistake or omitted, can cause a lot of head scratching (or worse) because your "per fect" program won't compile. The compiler sees exactly what you type.

COMMENTS
Comments help anyone (including you) who reads your code understand what it does. There arc two styles.
/* // */ Comments between /* and */ Comments between // and end of line

The compiler ignores all comments.

34

TEXT AND FORMATTING


Formatting such as spaces, tabs, carriage returns, etc. are ignored by the compiler. Formatting makes code readable to us. White space resulting from laying out a program so it appears better organized is a good thing. This comes from using spaces, tabs, and blank lines. The compiler will ignore white space. Use tabs for indentation instead of several spaces. The number of spaces per tab is usually adjustable. Three spaces per tab works well. ANSI (or standard) C is case sensitive, but CCS C is not.

35

BITS, BYTES, ETC.


BIT
One bit in memory or a register can represent 1 of 2 possible states, "0" or "1". In the world of digital electronics, it is convenient to build circuit elements which have two states, off or on. These states can be represented by "0" or "1".
POSITIVE LOGIC Binary Number 0 1 Voltage 0 volts (approximately) 5 volts (approximately) in a 5 volt system

The exact voltage ranges which represent 0 and 1 vary depending on the logic supply voltage and the integrated circuit logic chip family used (TTL, CMOS, etc.). The choice of using binary 0 to represent 0 volts is arbitrary. Positive logic is shown above. It can be done the opposite way which is called negative logic.

NIBBLE
A nibble consists of 4 bits and can represent 16 possible states. A nibble is typically the upper or lower half of a byte (most significant or least signi (leant nibble).

BYTE
A byte consists of 8 bits and is said to be 8 bits wide. 8-bit microcontrollers move bytes around on an 8-bit data buss (8 conductors wide).

BINARY
A binary' number with more than one bit can represent numbers larger than " I". How much larg er depends on the number of bits. An 8-bit binary number (byte) can represent 256 possible numbers (0 to 255). A 16-bit binary number can represent numbers from 0 to 65,535. If we use a byte to transmit information, we can transmit 256 possible combinations, enough to represent the 10 decimal digits, upper and lower case letters, and more. A commonly used code used to represent these characters is called ASCII (American Standard For Information Interchange).

36

Binary numbers arc based on powers of 2. The value of bit 0 is 2 = 1 if it contains a 1, or 0 if it contains 0. The value of bit 1 is 21 = 2 if it contains a 1, or 0 if it contains 0. The value of bit 3 is 23 = 8 if it contains a 1, or 0 if it contains 0, and so on. For a 16-bit binary number, bit 0 is the least significant bit, and bit 15 is the most significant bit. The following table shows the value of each bit position if it contains a " I

Bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Most Significant Least Significant

Bit
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Value
1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768

The value of a binary number contained in a 16-bit timer/counter would be determined by multiplying the contents of each bit by the value of each bit.

1 Bit

0 9

1 8

0 7

0 6

1 5

0 4

0 3

01 1

1 0

15 14 13 12 11 10

21

37

Counting up in binary goes like this: 0000 0001 0010


0011 0100 0101 etc.

You can use this information when you observe what happens to some LEDs used to display the count in a binary count up example program. PIC on-board timer/counters count in binary. A binary number may be used to represent byte-wide bit patterns sent to output ports.

HEXADECIMAL
Binary numbers which are two bytes long are difficult to recognize, remember and write without errors, so the hexadecimal numbering system is sometimes used instead. Think of hex as a kind of shorthand notation to make life easier rather than some kind of terrible math.

Hexadecimal Binary Decimal 0 1 2 3 4 5 6 7 8 9 A B C D E F 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Hex is sometimes used in this book to represent addresses in memory and is sometimes used to represent bytes of data. Using hex is not difficult. All you need is a little practice. One byte requires two hex digits. Note that the bits representing a byte are sometimes shown in groups of four. Note that the most significant hex digit is on the left. Hex numbers are denoted by "Ox" in this book. Some of the more recent Microchip literature uses the "h" to designate a number as hexadecimal.

38

To summarize:
Binary
1 1 1

Hex
11 1t

Decimal
ii t1

Nibbles
0000 0001 0101 1111 0 1 5 F 00 01 OF 10 FF 0000 0001 0100 FFFF 0 1 5 15 0 1 15 16 255 0 1 256 65535

Bytes
0000 0000 0000 0001 1111 0000 0001 1111 0000 1111 0000 0001 0000 1111

Two Bytes
0000 0000 0000 1111 0000 0000 0001 1111 0000 0000 0000 1111

Hexadecimal OxFFFF (the very top of the program memory address space in some microcon trollers) is much easier to write or remember than either 1111 1111 1111 1111 or 65535.

39

CONSTANTS
A literal constant may be defined as part of a built-in function such as:
output_b (OxOf); //defines hex byte OxOf and sends bit pattern // to port B

A symbolic constant may be defined using the key word const which is a modifier that can be applied to any numeric declaration.
const int LEVEL = 1 0 ; // defines integer type constant named // level with a value of 10

After the symbolic constant is defined, it is referred to by name in the program. The #define pre-processor directive may be used for defining constants.
#define MAX_SAMPLES 32

This method for defining constants is probably used more often than the key word const method. A convention in C is to use upper case letters in all constant names. Constants are stored in program memory and cannot be changed during program execution.

40

VARIABLES
Variables are memory locations used to store data. A variable must be defined prior to use in a program by using the assignment operator.
variable = data '---assignment operator

Variables are named according to the data type that will be stored in them, ie. an integer variable holds integer data, etc. (see next chapter, Data). The variable definition process tells the compiler how much memory space should be allocated for the variable being defined. A variable, as the name implies, may be changed during program execution and will be stored in a general purpose file register (RAM location) in the PIC microcontroller. Variables are either global or local. Global variables are defined outside a function and can be used by any function. Local variables are defined inside a function (after an opening brace) and used within that function (before the corresponding closing brace). It is considered good practice to use local variables wherever possible so you can control access to them. If a function needs to use another function's local variable, that variable can be passed to the function that needs it. Access to local variables is controlled, a good thing. Passing variables (arguments) will be discussed in a later chapter on the subject. Declaration of a variable will be demonstrated in the following chapter after data types have been explained. Naming of variables is explained in the chapter after that.

DATA
Basic data types used in this book are (see note below):
Character (ASCII) A character is a single ASCII character which may be represented by 8 bits. There are 256 ASCII characters. The characters most commonly used in microcontroller applications are contained in a table later in this chapter. Apostrophes indicate a character 'A' 'a' ' 1 '6' More than one character is called a string and is designated by quotation marks " " (see Strings chapter) Bit One bit Called inti, sometimes called short or boolean, can be called bit with use of typedef declarator (details follow) Can represent one of two numbers, "0" or "1" Byte 8 bits Called int8, byte Can represent 0 - 255 Hex OxOf Binary ObOOOOllll int 16 16-bit number Can represent 0 - 65535 Integer Whole numbers No decimal point Called int - same as int8 by default Can represent 0 - 255 Float Real numbers May have a decimal point Not used in this book Void Indicates no specific type

These are called data "types" and are used in type declarations to allocate the space required in memory for data storage.

42

Type qualifiers may be added to further delineate these basic number types, but we won't use them. Variables with negative values will not be discussed. Note: The definitions of C data types depends heavily on the source of the information. The degree of inconsistency is huge! My goal is to present what is needed to get going in a simpli fied way while still giving you a representative look at the wrorid of C. Using the CCS names (inti, int8, inti6) exclusively would simplify things, but you would not be able to read code found elsewhere. You will have some difficulty anyway, but I hope to keep it to a minimum. The following table lists the data types used in this book plus inti 6.

DATA TYPES How I Think character bit byte 16-bit binary integer CCS Name char inti int8 intl6 int

Bits 8 1 8 16 8

Range N/A 0, 1 0 - 255 0 - 65535 0 - 255

The integer data type may be assigned as follows


int a; //declares variable a as in integer type a = 2; //assigns initial value 2 to variable a int a = 2; //combined declaration, declares variable a and assigns // and initial value 2

The = sign is the assignment operator. Assignment examples arc sprinkled through the code examples that follow'. typedef is a declarator which can be used to create a new type name that can be used in declarations.
[type-qualifier] [type-specifier] [declarator] typedef inti bit; //use bit instead of inti

An example appears in the Writing Programs chapter in the Flags section.

ASCII CHARACTERS
The transmission of text requires that the text characters be encoded using a combination of binary bits. The most widely accepted text code is ASCII, the American Standard Code for Information Interchange, supported by the American National Standards Institute (ANSI). ASCII includes 26 upper case letters, 26 lower ease letters, the numerals 0 through 9, plus some punctuation characters and special characters. The following table includes the most commonly used characters. The table shows how each character may be represented by a hex byte as is sometimes necessary in microcontroller applications (driving an alphanumeric LCD for example).

JIBBLE 0x2
0x0 1 2 3 4 5 6 7 8 9 A B C D E F

UPPER NIBBLE 3 4 5 6 0 1 2 3 4 5 6 7 8 9 / < = > P Q R S T U V W X Y Z

7 P

i " # $ % Sc ' ( ) * +

A B C D E F G

H
I J K L M N O

/\

a b c d e f g h i j k 1 m n o

q
r s t u V w x y z

Example: 0x41 = A Note: 0x20 = Blank

44

NAMING CONSTANTS AND VARIABLES


Names for constants and variables are also referred to as identifiers (id). Rules: 1) All names used in a program must be unique. 2) Names must begin with a letter of the alphabet or an underscore. 3) After the first letter, names can be made up of letters, numbers, and underscores in any combination. An occasional capital letter may be used for clarity. 4) Names may be of any length up to a maximum of 32 characters. 5) ANSI (standard) C is ease sensitive, but CCS C is not. 6) The following key words are reserved for special uses in C. Do not use them for names.
auto break case char const continue default do doubl else entry enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while

7) In addition, the CCS compiler has the following non-standard reserved key words:
addressmod _fixed float32 float48 float64 inti int8 intl6 int32 int48 int64

45

A nice thing about C is that port pins may be named for their functions while writing programs which makes referring to them easy. The #define pre-processor directive may be used for defining constants.
#define GREEN LED PIN_B0

When it is time to write a line of code to toggle the green LED, the "green one" is easier to remember than the fact that the green LED is wired to port B, pin 0.
output toggle(GREEN_LED);

Another example is:


#define TD PIN_A1

RS-232 data is transmitted via pin ID rather than port A, pin 1.

46

OPERATORS - SHORT LIST


An operator is a symbol that instructs the C compiler to perform a specific operation on one or more operands. An operand is an expression that the operand acts on. This will become clear through usage. We will need only a few operators to get started writing C programs. They are: Assignment operator
equal sign variable = expression; Assign the value of expression to variable = not normally used in math, but it is legal to do so

Relational operators
== < <= 1= Equal to Less than Less than or equal to Not equal to

Used for comparison

Increment operator
Increment Used to increment a counter

Array operator
[ ] Used to index an element in an array

Mathematical operator
+ Addition Subtraction * Multiplication / Division

Structure operator
Dot operator or structure member operator

Logical operators
AND OR NOT

Grouping operator
() Grouping

47

TRUE vs. FALSE


A C expression is anything that evaluates to a numeric value. Expressions using relational oper ators evaluate to a value of either TRUE (1) or FALSE (0). Relational expressions are often used within if and while statements.
if (a<b) //compare a to b

If the expression evaluates as TRUE, the statement(s) following this line of code are executed. If the expression evaluates as FALSE, the statement(s) following this line of code are not executed. As another example:
while (input(PIN_A2)==1); //wait for switch to close

If the expression evaluates as TRUE, execution loops back. If the expression evaluates as FALSE, the while statement is terminated and execution passes to the statement(s) following the w'hile (condition).

DEVICE FILES
The CCS compiler contains a device file for each of the devices supported by the specific com piler you are using. The device file for the PIC16F818 (file 16F818.h) may be found in the com piler files (\PICC\Devices\16F818.h). I suggest printing a copy for reference. In this book, we will be mainly interested in the configuration fuses, port pin definitions, inter rupt functions, timer 0 functions, and A/D functions.

48

PRE-PROCESSOR DIRECTIVES SHORT LIST


C programs are processed by the compiler in two distinct steps. The first pass is a pre-processor step and the compiler will process lines of code that begin with #. The pre-processor lines may affect compiler settings or may control how the following text is interpreted. #defines will cause textural replacements. #include will be replaced with the contents of an entire file. Be aware that the preprocessor variables (identifiers) are not the same as normal C variables. When the preprocessor is done, there will be no pre-proccssor directives or identifiers left for the normal processor. All pre-processor directives begin with # and are used to convey information to the compiler. Following are the pre-processor directives used in this book: #define #defme id text id is the name you wish to define. In use, text replaces id everywhere it appears as the program is compiled. #define id constant #defme pre-processor directive may be used for defining constants. id is the name of the constant you wish to define.
#define MAXSAMPLES 32

#include #inelude <filename> or #includc "filename" A devicc file (.h file) is always included in a PIC microcontroller program. Device files are in the CCS compiler CD in the Devices directory. Print one for PIC 16F818 for reference. You may create include files of your own. #use delay (clock^s/w*/) #use delay (c\ock=speed) speed is a constant 1-100000000 (1 hz to 100 mhz) See CCS Reference Manual for details. #use delay (type=speed) #use delay (type=speed) type defines clock type speed is a constant 1 -100000000 (I hz to 100 mhz) See CCS Reference Manual for details.

49

#fiises #fuses options

options vary depending on the device and are listed in the device .h file for the
device of interest. See Configuration Register(s) Fuses chapter.

#bit id = x.y id is a valid C identifier x is a C variable or constant y is a constant 0-7 The I-bit variable is placed in memory at byte x, bit y. This is useful for referring to bits in special function registers.
#bit INTEDG = 0x81.6 intedg = 1 //interrupt edge select //interrupt on rising edge on RBO/INT pin

#byte #byte id = x id is a valid C identifier x is a C variable or constant


#byte portb = 0x06 //port B address

#use rs232 #use rs232 (options) See CCS manual for a long list of options. options are separated by commas. We will use baud rate and transmit pin designation.
#use rs232 (baud = 4800, xmit = PIN_A1)

The use of pre-processor directives will be explained in the Writing Programs chapter as they are needed.

50

INs AND OUTs OF DIGITAL I/O


PORT DATA DIRECTION
Port data direction (input vs. output) is controlled using a special register called the tristate or TRIS register. A TRIS register has 8 bits. There is a TRIS register for each port.

TRIS Register

Bit 7 6 5 4 3 2 1 0

Loading a "0" in a TRIS register bit makes the corresponding port bit an output. Conversely, a 1" results in an input. For most applications, built-in functions are used for input and output and TRIS register opera tions arc taken care of automatically by the CCS C compiler.

PORT READ/WRITE
Digital I/O operations using a PIC microcontroller and the CCS C compiler may be carried out in two ways based on: Using built-in functions provided in the CCS compiler for the purpose of accessing the ports. Working with variables stored in the port registers located in the PIC microcontroller's data memory. The variable controls the logic level of each port pin. In order to do this, we must tell the compiler where the port registers are located in data memory (port addresses), as this information is not included in the compiler in the form required to work this way. A person w'ith a hardware background will gravitate to the first method wrhile a person with a computer science background will gravitate toward the second method. Depending on your mind set and the application, you may find one of the methods easier/better. You can make up your own mind after being exposed to both philosophies. This will serve as a reminder that there is more than one wray to write a C program.

Built-In I/O Functions Method


The built-in functions of interest are:

INPUT_x()
OUTPUTxQ

value = input_x() x is port ID Inputs byte from port output_x (value) x is port ID Outputs byte to port
value = input (pin) output bit (pin, value) output high (pin) output low (pin) output toggle (pin) Reads pin Outputs 0 or 1 to pin Outputs 1 to pin Outputs 0 to pin Outputs 0 or 1 to pin

INPUT() OUTPUT_BIT() OUTPUT HIGH() OUTPUT LOW() OUTPUT_TOGGLE()

x - is port designation letter - a, b, etc.

pin - comes from device .h file - PINB1 for example


When using the built-in functions method, the port addresses are understood by the compiler and do not have to be defined by the user.

Port Register Accessed As A Variable (Port Address In Memory) Method


Using this method is based on treating the port registers themselves as variables. This makes it possible to do things like:
portb = porta; //make contents of port B = contents of port A

The poit addresses may be defined for use in a program in several different ways as shown in the examples in the chapter on Writing Programs. Using a structure to control a port is described in the chapter on Structures. When a port register is accessed as a variable, data direction must be specified in the program by writing to the port's TRIS register. The built-in function used for this purpose is: SET TRIS x() set tris_x (value) x is port ID I/O port direction

Don't try to understand all of this now. The subject will become clearer as you wrork through the programming examples in the chapter on Writing Programs.

52

CONFIGURATION REGISTER(S) FUSES


The configuration bits are located in flash memory outside the main part of flash memory used fo program storage. There are 14 or more configuration bits located in one or more configuration registers. The device programmer accesses these bits during the device programming procedure. The configuration options for each device (PIC16F818, etc.) are listed in pneumonic form in the devicc file for that devicc. You won't be concerned with which bit does what as the C compiler will take care of that for you. Fuses?? What fuses? In the olden days (not so long ago), memory chips wrcre made using fuseable links which were melted away (blasted) using a programmable read only memory (PROM) blaster. This terminology was used for the early PIC microcontrollers. For some reason, it is still used in the name for the directive which tells the C compiler what to do with the configuration bit (now in a flash memory register).

The #fuses pre-processor directive is used to determine what is written to the configuration regis ters) during device programming. #fuses options

options vary depending on the device and arc listed in the device .h file for the
device of interest. Later, when you are writing a program and you have a C project open (the device will have been selected), you may easily view the configuration options for the selected device by using: View>Valid Fuses The kinds of things determined when making configuration selections arc: Clock oscillator type selection Watchdog timer enabled/disabled Powcr-up timer enabled/disabled RA5/MCLR/Vpp pin function selection Brown-out reset enabled/disabled Lowr voltage programming cnabledMisabled Data code protection enabied/disabled Flash program memory write protection on/off In-circuit debugging enabled/disabled CCP module pin selection Flash program memory codc protection on;off Pre-proccssor directives are discussed in a chapter on the subject. RC Disabled Enabled MCLR Enabled Disabled Disabled Off Disabled CCP 2 Off RC NOWDT PUT MCLR BROWNOUT NOLVP NOCPD NOWRT NODEBUG CCP2 NOPROTECT

53

The CCS C compiler has default fuse selections. The defaults for the PIC16F818 are listed in the second and third columns in the list above. As it turns out, the default selections are what I would use, with the exception of the clock oscil lator. Using the internal clock oscillator is most convenient. We will use the default 31.25 KHz internal clock oscillator frequency for all examples except those involving a time delay or the use of timer 0. In those instances, we will use 4MHz as the internal clock oscillator frequency. The clock oscillator frequency is divided by 4 inside the microcontroller resulting in an instruction clock frequency of 1 MHz when the clock oscillator frequency is 4 MHz. This makes the math easy for calculating timer 0 intervals. In this book, clock oscillator type and frequency will be selected in one of three ways: 1) For programs with no time delay or use of timer 0:
#fuses intrc io

The pre-processor directive selects the internal clock oscillator option. By default, the frequency will be 31.25 KHz. 2) For programs using a time delay, we will use the internal clock oscillator operating at 4 MHz.
#use_delay(internal=4mhz)

The pre-processor directive selects the internal clock oscillator operating at 4 MHz. In addition to the delay, the compiler gives you:
#fuses intr_io setuposcillator(0SC4MHZ);

These two lines do not need to appear in your code. The pre-processor directive syntax is:
#use delay(type=speed) See the CCS compiler manual for details. type is the clock oscillator type. speed is a constant.

The compiler takes care of the clock oscillator fuses for us. 3) For programs using timer 0, we will use:
#fuses intrc_io

and
setup_osciliator(0SC_4MHz);

This will be illustrated by the programming examples that follow.

54

FUNCTIONS
main() FUNCTION
A function is a routine that performs a task. A function may come with the C compiler you are using or you may write one yourself. All C programs contain a "main" function which embodies the program that you create,

main()
{

program

The parentheses that follow a function name indicate that it is a function. Parameters are some times passed to functions as arguments contained within the parenthesis. In this example, there are none. Braces { } indicate what is included in the function, which is the whole program in this case.
void main (void) is commonly used in place of main() to begin C programs.

function

void main (void)

N------- main has no arguments

main does not return data

I choose not to use this method because in microcontroller applications, arguments are not required for the main program and no data is returned. The CCS compiler generates a warning when I do this. I choose to ignore it because using main() makes the code simpler and more readable. Simple is my goal.

55

FUNCTIONS
Functions are the basis of programming microcontrollers in C. The CCS C compiler comes with lots of functions designed to do specific tasks such as writing to a port, creating a time delay, set ting up an A/D converter, etc. The fact that these built-in functions are provided will save a lot of work and your projects will be completed sooner. Parameters may be passed to functions as arguments.
function ( ________ , ________ , ________ )

Calling a function may or may not return a variable. You may write a function to perform a specific task, test it, and store it away so it can be retrieved and used the next time you are writing code which includes that task.

56

BUILT-IN FUNCTIONS - SHORT LIST


The CCS C Compiler Reference Manual contains descriptions of the built-in functions contained in their compilers. Following is an abbreviated list of some functions we will need to get started writing programs. Some built-in functions require a special/specific include file(s) to work. Refer to the descrip tion of the built-in functions you are interested in using in your program. If a pre-processor directive file is needed, its name will appear under the "Requires:" heading in the built-in func tion description in the manual.

FUNCTION

SYNTAX

PRE_PROCESSOR DIRECTIVE (if required)

BIT_CLEAR() BIT_SET() BIT_TEST DELAY_CYCLES() DELAY_MS() DELAY US()

bit_clear(var,bit) bitset(var,bit) value=bit test(var,bit) delay cycles(count) delay_ms(time) delay_us(time)

#use delay #use delay

//clock frequency //clock frequency

For ports where x is port identification letter (ie. "a", "b", etc.): SET_TRIS x() INPUT_x() OUTPUT x(), etc. set_tris_x(value) value = input_x() output_x(value)

For individual port pins: INPUT() OUTPUT_LOW() OUTPUT_HIGH() OUTPUTBIT() OUTPUT TOGGLE( For interrupts: CLEAR_INTERRUPT() clearinterrupt(level) DISABLE_INTERRUPTS() disable_interrupts(level) ENABLE_INTERRUPTS() enable_interrupts(level) EXT_INT EDGE() ext_int_edge(source, edge) edge H_TO_L high to low transition at INT pin edge L_TO H low to high transition at INT pin For RS-232: PUTC() PRINTF() putc(cdata) printf(string) #use_rs232 and #use delay #use_rs232 and #use_delay value = input(pin) outputlow(pin) output high(pin) output_bit(pin, value) output_toggle(pin)

#int xxxx #int_xxxx

57

For controlling timer 0:


SETUP TIMER 0() SETTIMERO() GET_TIMER0() setuptimer_0(mode) set_timerO(value) value=get_timer0()

For controlling the A/D conversion process: SETUP_ADC(mode) SETUP_ADC_PORTS() SET_ADC_CHANNEL() READADC() setup adc(mode) setup_adc_ports (value) setup adc channel(chan) value=read_adc([mode])

STATEMENTS
EXECUTABLE STATEMENTS
An executable statement is a line of code that does something. Executable statements always end with a semicolon which indicates that the statement is executable. means "execute".

Blocks
A block is a group of one or more executable statements enclosed by braces. {
statementl; statement2; statementn; //block of statements

CONDITIONAL STATEMENTS
Conditional statements control program flow (see next chapter), if while do for switch

58

SEMICOLON USE RULES


Again, executable statements always end with a semicolon wrhich indicates that the statement is executable. means "execute". Conditional statements are not followed by a semicolon. if do for switch while has exceptions and has its own rules - see below, while do/while loop Semicolon required after w'hile (condition), while loop No semicolon after while (condition). Exception: a semicolon is required after while(TRUE) used in a while loop acting as a halt at the end of a program (executed).

Remember that there is no semicolon after a function name such as main() or del(). The programming examples in the chapter on Writing Programs will serve to illustrate.

59

PROGRAM DESIGN
This chapter will serve as an outline or overview of program design possibilities. Chunks of this information will be reprinted as appropriate in the following chapter on Writing Programs with accompanying programming examples. Showing the possibilities in one place will serve now as an overview and later as a reference. You may want to skim through this chapter the first time and then reread it a time or two as you go through the next chapter (Writing Programs).

60

PROGRAM DESIGN - CONTROL FLOW


if

if (condition) statement; or if (condition)

//single statement

{
statementl; statement2; statementn; //block of statements

If the condition is true, statement(s) is executed. If the condition is false, statement(s) is not executed. In either case, execution continues with the line of code following statement(s). Note that if(condition) and statement(s) are all part of the if statement.

The relational operators may be used as the basis of conditions in if and if/else conditional state ments to direct program flow.

Relational Operators Comparisons == Equal to > Greater than < Less than >= Greater than or equal to <= Less than or equal to != Not equal to

61

The logical operators && (and), || (or), and ! (not) may also be used as the basis of conditions in if and if/else conditional statements to direct program flow. The logical operators evaluate to TRUE or FALSE.
Logical Operators && and

II
! if/else

or
not

An if conditional statement may, as an option, contain an else clause. if (condition) statement(s)1; else statement statement(s)2;

//single statement or block //single statement or block

If the condition is true, statement(s)1 is executed and statement(s)2 is not executed. If the condition is false, statement(si) is not executed and statement(s)2 is executed. In either case, execution continues with the line of code following statement(s)2.

if/else statements may be cascaded to create a hierarchy for decision making. if (conditionl) statement(s)1; else if (condition2) statement(s)2; else if (condition3) statement(s)3; else statement(s) 4;

//single statement or block //single statement or block //single statement or block //single statement or block

For this sequence of if/else statements, only one will be executed and all that follow will be skipped over. The first if/else statement in the sequence that evaluates as TRUE will be executed. If conditions 1, 2 and 3 all evaluate as FALSE, statement 4 will be executed.

62

63

while loop while (condition) statement(s);

//single statement or block

Repeat code until condition becomes false. 1) Condition is evaluated first. 2) If condition is true, statement(s) is executed and execution loops back. 3) If condition is false, while statement is terminated and execution passes to the first statement following statement(s). 4) The statement(s) may not be executed at all if the condition remains true. We will use the while loop for simple situations where a simple condition such as a switch being open or closed governs program flow. while (TRUE); while (1) both work for circle = always 1 is true 0 is false while (1==1) also works

64

do/while loop do statement(s); while (condition); //single statement or block

1) The statement(s) is executed first. 2) Condition is evaluated. 3) If condition is true, execution loops back and the statement(s) is executed again. 4) If condition is false, while statement is terminated and execution passes to the first statement following while (condition). 5) The statement(s) in braces will be executed at least once. while (TRUE); may be used

65

for loop for (start expression; test expression; count expression) statement(s); //single statement or block start expression test expression count expression i = 0 i <= n i ++

//increment i

66

switch/case
When a variable can be one of several values, switch/case may be used instead of several if/else statements. As an option, a default may be used which takes care of any cases not otherwise included. If a default is not used and there is no match between expression and the cases given, execution passes to the first statement following the switch statement's closing brace. switch (expression)

{
case 0: statement(s); break; case 1: statement(s); break; default: statement(s); break;

}
Expression must evaluate to an int8, int16, or a char.

break statement - causes control to skip past the end of a loop or switch statement

While ()

{
--- break;

continue statement - causes control go back to the beginning of a loop

-- *- While ( )

{
--- continue;

}
return statement - used to return from a function call (see modular programming section which follows) goto goto label;

label: statement; Execution branches to location identified by label within the same function. if (conditionl) goto labell; else statement;

labell: statement;

If you want the label to appear on a line by itself, you can place a semicolon after the label. A semicolon by itself is a null statement (does nothing). label: ; statement;

C purists don't like the use of goto statements and go all out to avoid using them.

RULE
Variables must be declared at the beginning of a block where a block is an open { to a close }. This is a syntax rule. Variables can be initialized at any time. Failure to do this will result in a lot of error messages being generated!

68

MODULAR PROGRAMMING
C is designed to encourage (force) modular or structured programming. Programs are construct ed using modules called functions. Each function performs a specific task. In assembly language, the modules are called subroutines. The main function calls functions to perform tasks. Further, any function can call any other function. The functions that you crcate must appear ahead of main () in your source code as they must be defined prior to use.
functl()

{
return; //return to calling function, main

}
funct2()

{
return; //return to calling function, main

}
funct3()

{
return; //return to calling function, main

}
main()

{
functl(); funct2(); funct3();
//calls function 1 //calls function 2 //calls function 3

} The return statement sends execution back to the calling function. For other than very simple programs, the main function is used solely to call each of the func tions designed to perform the tasks that the program is required to accomplish.

69

WRITING PROGRAMS
PROGRAMMING CONCEPTS
The PIC16F818 microcontroller will respond to a series of coded instructions stored in program memory. When a designer (which may be you) thinks of something which he or she would like to control, the natural thing to do is to think through the control process in logical steps. Creating a How chart is a good way to visualize these steps. The control process might consist of sensing out side world events such as light vs. darkness, cows passing through a gate, temperature, a key stroke etc., testing the data w'hich has come from the sensors followed by taking one of two pos sible program paths based on the test results, and controlling some outside device such as a digi tal display, indicator light, motor, heater, etc. Instructions for repetitive operations can be repeated in long strings, but that wastes valuable memory space. It may even result in a program which is too long to fit in the available memory space. Instead, one sequence of instructions can be used over and over in a loop and the micro controller goes 'round and 'round until something forces it to stop. Loops can go around forever or until the plug is pulled or an anvil is dropped on the microcontroller chip. Or until a counter counts up to a predetermined number. Or until a test result says to move on.

Note: The first line of some example programs in this chapter contains "ala pict3 . asm" or similar. This indicates that the C program performs the same function as an assembly language program "pict3. asm" in our books Easvj PIC'n and Easy Microcontrol'n.

70

An example of the use of a loop is a program used to read an input port and display the data received (or the status of the lines) at an output port.

Loops arc useful when it is necessary to perform an operation a certain number of times (n).

71

The use of a loop prevents having to write the code n times and the requirement for memory space to store it. Another technique for keeping C programs short and manageable is the use of functions. If the same task is to be performed in two or more places in a program, the function code can be wTitten once and stored in one location. When the points in the program arc rcachcd where the func tion is to be used, it is called. The last statement in a function of this type is always a return statement. The program then continues w'here it left off. An important concept to keep in mind when using microcontrollers is that they can only do one thing at a time, one very simple thing. They execute many very simple instructions and they do it blindingly fast. The P1C16F818 executes roughly a million instructions every second with a 4MHz clock oscillator. In situations where events of interest to the microcontroller occur only once in a while, perhaps randomly, it may be desirable to use a microcontroller feanire called the interrupt. In simplified form, if an event occurs in the outside world which demands the microcontroller's attention, the sensor monitoring the event can be wired to direct a signal (pulse) to an interrupt line (pin) on the microcontroller. Wrhen the signal arrives, the microcontroller drops what it was doing (after finishing the instruction it was executing), and then jumps to a special program called an inter rupt service routine. The purpose of the routine is to do whatever the designer/programmer thinks is required when the outside event occurs. Microcontrollers do only one thing at a lime, but they can be instructed to drop one task, take care of another, and resume the original task. Interrupts arc a special area closely tied to the hardware used to make their occurrence known, so they will be discussed in detail in the Interrupts chapter of this book.

PROGRAMMING EXAMPLES
The best way to learn how microcontrollers arc used is to think of applications, write programs to implement them, and C what happens. In this chapter, we will start out with very simple pro grams to demonstrate the concepts we have just discussed. By the time you have finished this chapter, you will be able to think in microcontroller terms and will see microcontroller applica tions in your work or hobbies. You will also be able to visualize the methods for implementing microcontroller solutions. Simple programs used as examples will illustrate the use of the various types of functions and some executable statements.

72

The first example program (introduced earlier) turned on 4 of 8 LEDs assumed to be connected to port B and turned the remaining 4 off. It did this by: Setting up the clock oscillator. Writing a byte to the port B register. The hexadecimal byte OxOF is equivalent to binary 00001111. A " 1" in a bit position of the port register causes that bit/pin to be logic high (+5 volts in this case) which results in current flowing through the LED connected to it. A "0" in a bit position of the port register causes that bit/pin to be logic low (0 volts) which results in zero current through the LED connected to it. In the following version of the same program, we will use the default clock oscillator frequency (31.25 KHz). A flow chart will help you visualize what this simple program does.

////first C program ala pictl.asm Csla #include <16F818.h> #fuses INTRCIO //remaining fuses by default //internal clock osc with I/O on RA7, RA6 //watchdog timer disabled power-up timer enabled //code protection off brown-out reset enabled //mclr enabled low-voltage programming disabled //no EE data protection write protection off //no debug CCP1 function pin RB2 (arbitrary) //clock oscillator frequency is 31.25 KHz by default main( )

{
output b (OxOf); while (TRUE); //bit pattern to port B //circle, always

} Let us examine the various parts of the program. The comment on the first line provides information about the program. The #include pre-proccssor directive serves to include the PIC16F818 device header file in the program. The #fuses pre-processor directive selects the internal clock oscillator option. The default inter nal clock oscillator frequency is 31.25 KHz. The main() function is present/required in all C programs.

73

Between the two curly braces are two program statements which will be executed. Notice that each statement is followed by a indicating to the compiler that the statement is to be executed. The literal OxOf is defined as part of the outputb (OxOf) statement. This is the byte written to port B. Hex OxOf is equivalent to binary 00001 111. Bit 0 is the " 1" on the right. Bit 7 is the "0" on the left. The port has 8 lines or pins numbered 0 (least significant bit) through 7 (most significant bit). The port and its register are 8 bits wide. There is no halt function in C, so we will have to fake one. while (true); is a conditional statement. The while loop repeats until the condition becomes false which will never happen here as the condition is defined as TRUE. The result is an endless or infinite loop. The loop goes 'round and 'round forever, or until power to the PIC microcontroller is turned off. If the while (true); statement were not there, the compiler would create an instruction to put the microcontroller to sleep at the end of main(). A ';' appears at the end because while (true) ; is executed. The next program is the same as the previous one with the exception that the literal representing the bit pattern sent to port B is defined as a binary number.

////first C program #include <16F818.h> #fuses INTRCIO main()

ala pictl.asm

Cslb

{
outputb (ObOOOO1111) ; while (TRUE); //bit pattern to port B //circle, always

74

SIMPLE DATA TRANSFERS


Input ports are read using data transfers. The next example reads the data from port A and writes that data to port B. DIP switches arc assumed to be located at port A, bits 3, 2, 1, 0 (least significant 4 bits) and LEDs are assumed to be located at port B. Check to be sure that the jumper which connects pin RAO to the pullup resistor and DIP switch is in place . Port A, bits 7,6,5,4 are ignored for this experiment. We will declare a byte variable "pattern". This must be done at the beginning of the block. A block is an open {to a close }. Failure to do this will generate compiler error messages. The input_a () built-in function requires a name for the variable obtained by reading port A. The program stores the data from port A (bit pattern) in the variable "pattern". Notice that this'fs done by making pattern = port A. Then the data in the variable pattern is output on port B. After that, a continuous loop is executed.

////data transfer demo ala pict2.asm #include <16F818.h> #fuses INTRC_IO main()

Cs2a

{
byte pattern; pattern = input_a(); output_b (pattern); while (TRUE); //declare variable pattern (a byte) //read port A bit pattern //bit pattern to port B //circle, always

} Set the switches at the input port and run the program. Compare the resulting bit pattern at the display (least significant 4 bits) with the pattern you set in. Change the input pattern and rerun the program by resetting the microcontroller.

75

LOOP - ENDLESS
In the first two examples, we used an endless loop while( true ) to fake a halt (the PIC16F818 docs not have a halt instruction). The PIC16F818 sits in an endless loop (circle).
while (TRUE); while (1); both work for circle = always ^--- 1 is true 0 is false

Next we will use a while loop in a different way. The program will read the input port A and continuously display the lowest 4 bits of port A at the lowest 4 bits of port B. Check to be sure that the jumper which connects pin RAO to the pullup resistor and DIP switch is in place . Port A, bits 7,6,5,4 are ignored for this experiment. This time, the statements inside the braces following while ( true ) repeat over and over in a loop. (

The program does the same thing as the previous one cxccpt it uses a while loop so that the sta tus of port A may be displayed via the LEDs on port B on a continuous basis. If a switch posi tion on one of the 4 low order bits changes, the result will be indicated on port B.

while loop while (condition) statement(s);

//single statement or block

Repeat code until condition becomes false. 1) Condition is evaluated first. 2) If condition is true, statement(s) is executed and execution loops back. 3) If condition is false, while statement is terminated and execution passes to the first statement following statement(s). 4) The statement(s) may not be executed at all if the condition remains true.

////data transfer demo - while loop #include <16F818.h> #fuses INTRC_IO main( )

ala pict2.asm

Cs2b

{
byte pattern; while (TRUE) {pattern = input_a(); output_b (pattern);} //declare variable pattern (a byte) //read port A bit pattern //bit pattern to port B

Vary the switch settings while the program is running. This loop will run forever unless you reset the microcontroller or pull the plug (sometimes called "absolute reset"). ** We will use the while loop for simple situations where a simple condition such as a switch being open or closed governs program flow.

Next, we will use a do/while loop which will produce the same results.
do/while loop do statement(s); while (condition) //single statement or block

1) The statement(s) is executed first. 2) Condition is evaluated. 3) If condition is true, execution loops back and the statement(s) is executed again. 4) If condition is false, while statement is terminated and execution passes to the first statement following while (condition). 5) The statement(s) in braces will be executed .at least once. while (TRUE); may be used

////data transfer demo - do/while loop ^include <16F818.h> #fuses INTRC 10 main()

ala pict2.asm

Cs2c

{
byte pattern; do //declare variable pattern (a byte)

{
pattern = input_a(); output_b (pattern); //read port A bit pattern //bit pattern to port B

}
while (TRUE);

78

Up to this point, built-in compiler functions have been used to access the ports. Sometimes it is advantageous to access the port registers as variables.
portb = porta
A

This requires defining the byte variable "porta" address ahead of use in the program. Port addresses are defined using #byte directives ahead of main(). Port A is in data memory and is said to be "memory mapped". The same is true for port B. The ports are Special Function Registers (SFR). Ports may be read or written to like any other data memory (RAM) location. This requires telling the compiler where they are located in data memory. In the following example, the port memory locations are made known to the compiler using using #byte pre-processor directives ahead of main(). Port addresses may be obtained by using Microchip's device data books or View>Special Registers in the CCS compiler. Accessing the port registers as variables necessitates specifying the direction (input vs. output) '' of the port pins. This is done using the set_tris_x() built-in function. Recall that a "0" in a bit position in a TRIS register makes the corresponding port pin an output. A " 1" makes the pin an input. The next example does the same thing as the previous one by accessing the port registers.

////data transfer demo - out = in #include <16F818.h> #fuses INTRC_IO #byte porta = 0x05 #byte portb = 0x06 main()

ala pict2.asm

Cs2d

//port A address //port B address

{
set_tris_a (Oxff); set_tris_b (0x00); do //port A inputs //port B outputs

{
portb = porta; //read port A bit pattern, display at // port B

}
while (TRUE);

79

Port addresses may also be defined using an include file which contains the #byte directives. You can create an include file yourself (named ports.c in this example). The include file looks like this:
////port address definitions #byte porta = 0x05 #byte portb = 0x06 ports.c //port A address //port B address Cs2e

The include file is used in the following program:


////data transfer demo - out = in #include <16F818.h> #fuses INTRC_IO #include <ports.c> main() ala pict2.asm

//port addresses, A & B, 16F818

{
set_tris_a (Oxff); set_tris_b (0x00); do

.
//port A inputs //port B outputs .

, v.

{
portb = porta; //read port A bit pattern, display at // port B //circle, always

}
while (TRUE);

Another method is to use a feature of the CCS compiler. View>Special Registers accesses the Device Table Editor. Select device. Make include file by clicking on the Make Include File icon.
\

Of course you will need to use the definitions found in the file. Another way to define the port addresses is to use the get environment getenvQ built-in function. The getenv() function is used here as part of an assembler directive which appears ahead of main(). "SFR:porta" in the example refers to special function register port A. Port A is one of the special function registers (Microchip terminology). The compiler knows where in the environment (memory map) port A lives by looking in the device header file (always included in the code), so the #byte directive as used here can find the address.

////data transfer demo - out = in #include <16F818.h> #fuses INTRC_IO #byte porta=GETENV("SFR:PORTA") #byte portb=GETENV("SFR:PORTB") main()

ala pict2.asm

Cs2f

//get port A address //get port B address

{
set_tris_a (Oxff); set_tris_b (0x00); //port A inputs //port B outputs

I {
portb = porta; //read port A bit pattern, display at // port B //circle, always

}
while (TRUE);

81

LOOP WITH COUNTER


I i

It is often necessary to perform some operation a specified number of times. To do this, we will use a counter. Each time the operation is performed, a one is added to the counter. This is called incrementing the counter. When the number in the counter becomes equal to the number of times the operation is to be performed, the program can stop or go on to something else. A sim ple example will illustrate this concept.
for loop for (start expression; test expression; count expression) statement(s); //single statement or block start expression test expression count expression i = 0 i <= n i ++

//increment i

For this example:

Circle

82

Note that 0 counts, so the counter will be incremented 5 times and will contain 4 after it is incre mented the 5th time.

////loop with counter demo #include <16F818.h> #fuses INTRC_IO main()

ala pict3.asm

Cs3

{
byte i; output_b (0x00); for (i=0; i<=4; i++) {output_b (i); //declare variable i //initialize port B //display count i 5 times, final count // is 4 //circle, always

>
while (TRUE);

Port B will indicate 0x04 in binary (00000100). This program illustrates a very important concept, the power of microcontrollers to make deci- v sions. A comparison is made to see if the counter contents is less than or equal to a constant. If so, the program loop continues until the counter hits the number we have chosen. If not, the pro gram ends in a continuous loop. The comparison determines the flow or path of the program. *' r milliseconds. v Load the program and run it to C what happens. It will be finished in a few Port B should contain 0x04. You may want to try other numbers in the counter. "<

LOOP UNTIL
Another use for a loop is a loop-until situation.

while loop while (condition) statement(s);

//single statement or block

Repeat code until condition becomes false

Following is a simple while loop:


while (input(PIN_A2)==1) ; //go low? wait for ready switch to close

+5VDC

On reset, the "ready" switch is open and pin A2 is pulled up to +5 volts via a pullup resistor (pin A2 = 1). The microcontroller sits in the loop until the "ready" switch is closed which connects pin A2 to ground (pin A2 = 0). At that point, the condition for while becomes FALSE (input (PIN A2) no longer equals 1) and execution proceeds to presenting a bit pattern to port B. which turns on the LED connected to pin B2.

////simple while loop demo #include <16F818.h> #fuses INTRC_IO main()

{
output_b (0x00); while (input(PIN_A2)==1); output_b (ObOOOOO100); while (TRUE); //clear port B //go low? wait for ready switch to close //indicate ready switch closed //circle, always

Notice that the condition for the while loop (input ( pin_a2 ) ==i ); evaluates as TRUE when port A, pin 2 is high, logic 1. Another way to write this is simply:
while (input(PIN_A2)); //go low? wait for ready switch to close

The condition for the while loop is (input ( pin_a2 )); As before, this evaluates as TRUE when port A, pin 2 is high, logic I. Writing" == l" makes the code more readily understandable. Both methods work. You will see both as you look at other people's code, so you need to be familiar with both methods.

85

COMPARISONS
Two numbers may be compared using the relational operators.

Relational Operators Comparisons == Equal to > Greater than < Less than >= Greater th^n or equal to <= Less than or equal to != Not equal to

A number can be compared with a literal value N to determine their relative magnitudes. The direction the program takes depends on the results of the comparison. The if/else statement is used to accomplish this.
if/else An if conditional statement may, as an option, contain an else clause. if (condition) statement(s)1; else statement statement(s)2;

//single statement or block //single statement or block

If the condition is true, statement(s)1 is executed and statement(s)2 is not executed. If the condition is false, statement(s1) is not executed and statement(s)2 is executed. In either case, execution continues with the line of code following statement(s)2.

J
86

Here is a simple program to demonstrate a method of testing the comparison procedure. LEDs are assumed to be connected to port "B" for use as an indicator.
////comparison demo #include <16F818.h> #fuses INTRC 10 main ( ) ala pict5.asm Cs5

{
byte less = 0x01; byte nope = 0x02; int a = 2; int b = 4; output b (0x00); if (a<b) {output_b (less); //used to indicate "less" result //used to indicate "nope, not less" // result //declare integer variable a //declare integer variable b //initialize port B //compare a to b and display result // via LEDs <

}
else {output_b (nope);

}
while (TRUE); //circle, always

"00000001" displayed via the LEDs indicates "less". Try changing a to 5 and C what happens. *

SWITCH/CASE
When a variable can be one of several values, switch/case may be used instead of several if/else 1 statements. As an option, a default may be used which takes carc of any cases not otherwise included. If a default is not used and there is no match between expression and the cases given, execution passes to the first statement following the switch statements closing brace.
switch (expression)

{
case 0: statement(s); break; case 1: statement(s); break; case 2: statement(s); break; case 3: statement(s); break; default: statement(s); break;

} Expression must evaluate to an int8, int 16, or a char.


,<

In this example, expression should evaluate to 0, 1,2, or 3. If not, the default statement(s) will be executed.

87

A break statement causes control to skip past the end of the switch statement.

////switch/case demo #include <16F818.h> #fuses INTRC_IO main()

Cs6
a

{
int num = 3; output_b (0x00); switch (num) //declare integer = 3 for test //initialize port B

{
case 0: output_b break; case 1: output_b break; case 2: output_b break; case 3: output b break; case 4: output_b break; default: output_b (0x00); //display 0 via port B LEDs

(0x01);

//display 1 via port B LEDs

(0x02);

//display 2 via port B LEDs

(0x03);

//display 3 via port B LEDs

(0x04);

//display 4 via port B LEDs

(Obi1111111);

//display Obllllllll via port B LEDs //circle, always

}
while (TRUE);

Because I made num = 3, the result of execution will be case 3. Execution of case 3 results in binary 3 being displayed via the LEDs.

88

FUNCTION CALLS AND TIME DELAYS


Sometimes it is necessary to wait a specified period of time for something to happen. This might be waiting for switch contacts to stop bouncing or holding an EPROM control line high for a specified time as part of the programming procedure. Time interv als can be measured using hardware (covered later) or by software using a built-in time delay function. The accuracy of the interval depends on the accuracy of the PIC16F818 system clock. For accurate delays, a crystal-controlled clock oscillator is recommended. The next two examples will illustrate the use of time delays and function calls. The program counts in binary and displays the count at eight LEDs via port B. If the counting were done at full speed, the LEDs would appear to.be all on all of the time. Instead, a built-in time delay function will be used to provide a time delay between counts so that each count will be dis- * played for about 500 milliseconds. Delays in the milliseconds range may be created easily by using the built-in delay__ms() function. delayms(time) Time is the desired interval in milliseconds. Time may be a variable 0 - 65535 (int 16) or a constant 0 - 65535. This delay is created by the compiler in software and does not use a hard ware timer/counter such as timer 0. If an interrupt occurs during the time interval, the time required to service the interrupt will be added to the time interval.

////binary counting demo //// uses time delay #include <16F818.h> #use delay(internal=4mhz) main() byte count = 0; do output_b (count); delayjms(500); count ++; while (TRUE);

ala pictl2.asm

Cs7a

//clock oscillator internal, frequency

//declare counter, count in binary

//display binary count via LEDs //delay 500 milliseconds //increment counter

} For programs using a time delay, we will use the internal clock oscillator operating at 4 MHz.
#use_delay(internal=4mhz)

The pre-processor directive selects the internal clock oscillator operating at 4 MHz. In addition to the delay, the compiler gives you:
#fuses intr_io setup_oscillator(0SC_4MHZ);

These two lines do not need to appear in your code. The pre-processor directive syntax is:
#use delay(type=speed) See the CCS compiler manual for details. type is the clock oscillator type. speed is a constant.

The compiler takes care of the clock oscillator fuses for us.

We can improve this program by creating a time delay function, placing that function ahead of
main (), and calling it from within main(). This is in keeping with our goal of creating modular

programs.

////binary counting demo ala pictl2.asm //// uses time delay function call #include <16F818.h> #use delay(internal=4mhz) //clock oscillator del ( ) //delay function

Cs7b

internal,

frequency

{
delay_ms(500); return; //delay 500 milliseconds

}
main()

{
byte count = 0; do //declare counter, count in binary

{
output_b (count);

del();
count ++;

//display binary count via LEDs //call delay function //increment counter

}
while (TRUE);

Note that this program loops until the microcontroller is reset. The "count" register is an 8-bit counter which has 28 = 256 possible combinations of 0's and l's.

91

BIT-LEVEL I/O USING BUILT-IN FUNCTIONS


The next example uses the output highQ and output_low() built-in functions in conjunction with the time delay function created in the previous example to toggle bit 0 of port B on and off. The function dei() is called twice by main(). By wrriting the time delay as a function which can be called saves writing a delay statement twice in main().

////output_high(), output_low() demo

Cs8a

I I I I uses time delay via function call


#include <16F818.h> #use delay(internal=4mhz) del ( ) //dock oscillator //delay function internal, frequency

{
delay_ms(500); return; //delay 500 milliseconds

}
main()

{
output_b (0x00); do //clear port B

{
output__high (PIN B0 ) ; del(); output_low(PIN_B0); del( ) ; //toggle port B, bit 0 //call delay function //toggle port B, bit 0 //call delay function

}
while (TRUE);

Next, vve will use the output_toggle() built-in function to accomplish the same thing as in the previous example.
A

I I I /output_toggle() demo 1111 uses time delay via function call


#include <16F818.h> #use delay(internal=4mhz) del () //clock oscillator internal, //delay function //delay 500 milliseconds

Cs8b

frequency

{
delay_ms(500); return; main()

{
output_b (0x00); do //clear port B v

{
output_toggle(PIN_B0); del(); //toggle port B, bit 0 //call delay function

}
while (TRUE);

In the comparison example presented earlier, the if/else statement was used. In the following example, if will be used by itself.
if

if (condition)' statement; or if (condition)

//single statement

{
statementl; statement2; statementn; //block of statements

If the condition is true, statement(s) is executed. If the condition is false, statement(s) is not executed. In either case, execution continues with the line of code following statement(s). Note that if(condition) and statement(s) are all part of the if statement.

The schematic for this experiment is:

+5VDC

94

An if statement is used by itself to check the position of a switch as the code is executed. If the switch is open, port A, pin 2 will be high and the condition for the if statement will evaluate to "1" or TRUE. The statement following the if statement (really part of the if statement) will be executed and the LED at.port B, pin 2 will be on. If the switch is closed, the if statement will evaluate to "0" or FALSE. The statement following the if statement will not be executed and the LED at port B, pin 2 will be off.

////simple if demo #include <16F818.h> #fuses INTRC_IO main()


{ '

Cs9a

output_b (0x00); if (input(PIN_A2)) output b (ObOOOOO100 ) ; while (TRUE);

//clear port B //high? //indicate switch open //circle, always

1) Power-up with switch closed on port A, pin 2 - observe LED. 2) Power-up with switch open on port A, pin 2 - observe LED. 3) or change switch position and reset. The logical operators && (and), || (or), and ! (not) may be used as the basis of conditions in if and if/else conditional statements to direct program flow. The logical operators evaluate to TRUE or FALSE.

Each of them will be used in the following three examples.

The NOT logical operator negates the logic of the expression it operates on.
! expression will evaluate to FALSE if expression is TRUE.

////simple if demo /include <16F818.h> #fuses INTRC_IO main()

Cs9b

{
output_b (0x00); if (1 input(PIN_A2)) output_b (ObOOOOOlOO); while (TRUE); //clear port B

//low?
//indicate switch closed //circle, always

} 1) Power-up with switch open on port A, pin 2 - observe LED. 2) Power-up with switch closed on port A, pin 2 - observe LED. 3) or change switch position and reset.

The next two experiments require two switches.

+5VDC

96

Using the AND logical operator results in the following:


expression 1 && expression2 will evaluate to TRUE if both expression! and expression? are TRUE.
////simple && logical operator demo #include <16F818.h> #fuses INTRC_IO main() CslOa

{
output_b (0x00); if (input(PINAl) && input(PIN_A2)) < output_b (ObOOOOO100); while (TRUE); //clear port B .//both high? //indicate both switches open //circle, always

} 1) Power-up with switches closed on port A, pin 1 and port A, pin 2. 2) Open switch on port A, pin 1 and reset. Observe LED. 3) Open switch on port A, pin 2 and reset. Observe LED:

Using the OR logical operator results in the following: expression 1 || expression2 will evaluate to TRUE if expression 1 or expression2 is TRUE.
////simple || logical operator demo #include <16F818.h> #fuses INTRC_IO main() Csl0b*~

{
output b (0x00); //clear port B if (input(PINAl) || input(PIN_A2)) //high? outputb (ObOOOOO100); //indicate one switch or other or both // open while (TRUE); //circle, always

1) Power-up with switches closed on port A, pin 1 and port A, pin 2. 2) Open switch on port A, pin 1 and reset. Observe LED. 3) Open switch on port A, pin 2 and reset. Observe LED. 4) Reset and try other possibilities.

if/else statements may be cascaded to create a hierarchy for decision making.


if (conditionl) statement(s)1; else if (condition2) statement(s)2; else if (condition3) statement(s)3 ; else statement(s)4 ;

//single statement or block //single statement or block //single statement or block //single statement or block

For this sequence of if/else statements, only one will be executed and all that follow will be skipped over. The first if/else statement in the sequence that evaluates as TRUE will be execut ed. If conditions 1, 2 and 3 all evaluate as FALSE, statement 4 will be executed. To try this out, let's assume that we want to make one of three choices known to the microcon troller by means of three switches. One, and only one switch may be open, indicating our choice. In response to a switch sensed as open (logic 1), a corresponding LED is turned on. Looking at the schematic will make this clearer.

+5VDC

98

////simple if/else, else, else demo #include <16F818.h> #fuses INTRCIO main()

Csll

{
output_b (0x00); if (input(PIN_A1)) output_b (ObOOOOOOlO) else if (input(PIN_A2)) output_b (ObOOOOOlOO) else if (input(PIN_A3)) output_b (ObOOOOlOOO) else output_b (ObOOOlOOOO) while (TRUE); //clear port B //high? //indicate port A, pin 1 switch open //high? //indicate port A, pin 2 switch open //high? //indicate port A, pin 3 switch open //indicate statement 4 //circle, always

1) Power-up with switches closed on port A, pins 1, 2, 3 - observe LEDs. 2) Change switch positions, reset - observe LEDs - test program operation.

Next we will try a program which uses bit manipulation and port bit-level built-in I/O functions. The objective of the program is to look at an input port line and when it has a 1 on it, output a 1 on an output port line (if sense X, then turn on Y). Bit 3 of port A is arbitrarily chosen as the input line to be sensed and bit 2 of port B is arbitrarily chosen as the output line to be turned on. The input line switch is closed before the program is run. The output port line is cleared to 0 at the beginning of the program so the LED will be off when the program is initiated.

+5VDC

100

////bit manipulation demo #include <16F818.h> #fuses INTRC_IO main()

ala pictlO.asm

Cs 12

{
output_b (0x00); while (input(PIN_A3)==0); output_high(PIN_B2); while (TRUE); } //clear port B, LEDs off //switch closed? //no, turn on LED //circle, always

whi^e ( bit_test ( porta, 3) ==0); is ah example of loop until something happens. When the

switch is closed (initial condition), port A, pin 3 is connected to ground = logic 0. When the switch is opened, the pin is pulled up to +5 volts and the while condition changes causing pro gram execution to move to the next statement. , .. 1) Power-up with switch closed - observe port B, bit 2 LED. 2) Open switch - observe port B, bit 2 LED. The next program makes port B, pin 2 the same as port A, bit 3 using a while loop. Notice the use of the input() function and the use of the output bit() function.
value = input (pin) output_bit (pin, value)

In the program:
{output_bit ( P I N B 2 , input(PINA3));}

The value of port A, pin 3 is output on port B, pin 2 in a while loop.

////output pin = input pin #include <16F818.h> #fuses INTRC_I0 main()

Csl3

{
output_b (0x00); //clear port B, LEDs off while (TRUE) {output bit (PIN_B2, input(PIN_A3));} //make port B, pin 2 same as // port A, pin 3 //output bit (pin, value) //value in this case is input(PIN_A3)

>

The next example program illustrates an application for bit manipulation in event counting using port bit-level built-in functions. Events represented by a series of switch closures can be counted. The example program will count the number of times the bit 0 switch used in the pre vious example is opened allowing the port A bit 0 input line to be pulled up to logic 1. The count is displayed in binary on LEDs connected to port B.

+5VDC

102

Start with switch closed, bit = 0. Loop 'til bit = 1 which means a transition from 0 to 1 has occurred (leading edge of pulse detected). Loop 'til bit = 0 which means a transition from 1 to 0 has occurred (trailing edge of pulse detected). The pulse is counted when the trailing edge has been detected.

Leading Edge

Trailing Edge

////event counting demo #include <16F818.h> #fuses INTRC_IO main()

ala pictll.asm

Cs 14

{
byte count; output_b(0); count=0; do //declare counter, count in binary //clear port B //clear counter

{
while (input(PIN_A0)==0); while (input(PIN_A0)==1); count ++; output_b (count); //go hi? //go lo? //yes, pulse received, // increment counter //display counter contents via LEDs

}
while (TRUE);

/ Depending on the switch used, contact bounce may be experienced which will result in multiple pulses being generated and counted for one switch open/close cycle. Switch contacts sometimes bounce when the switch is actuated. This phenomenon is commonplace and can be compensat ed for in software (not explored here). If you are getting too many counts per switch open/close cycle, contact bounce is the culprit. A solution to this problem is to use the positive-going output of the pulser circuit described in Appendix A connected directly to pin RAO. It will output one clean pulse per push on the switch lever.

BIT MANIPULATION USING BIT MANIPULATION FUNCTIONS


Built-in bit manipulation functions can be used to Set, clear, or test an individual bit.

Bit Set/Clear
The bit set and bit clear functions operate on a selected bit in a selected register. bit_set (var, bit ) bit clear (var, bit) As an example, bit 3 of a byte variable called reg can be set (made logic 1 or high) by:
bit_set(reg,3); //set reg, bit 3

Bit Testing
A bit in a file register may be tested using the bit test function. , . value = bit test (var, bit) The test may be used in conjunction with a while loop or if/else construct as examples. Bit set, bit clear and bit test will be used in the next example.

104

////bit manipulation demo #include <16F818.h> #fuses INTRC_IO main()

Cs 15

{
byte reg; byte result; reg = ObOOOOllll; bit_set (reg, 4 ) ; bit_clear (reg,0); result = bit_test (reg,3); if (result == 1) {bit_set (reg,5); //declare "register" to play with //result of bit test //register contents to modify

//00011111 //00011110
//test bit, result in variable bit //00111110 will happen

}
else {bit_clear (reg,l); //00011100 won't happen

}
output_b (reg); while (TRUE); //circle, .always

You will be able to see the 6 least significant bits via the LEDs.

Flags
A flag is a one-bit register which is set (1) or cleared (0) by execution of one or more types of instructions or by operation of hardware inside the microcontroller. Some flags arc built into the microcontroller. In the case of a peripheral such as a timer, setting or clearing generally takes place automatically. Others are created in software to indicate results of program execution. After execution of an instruction, the affected Hag may be tested to see if it was set or cleared. The path taken by the program depends on the status of the flag being tested. In the following example, we will create a 1-bit variable called "flag", change its contents, and display the contents at bit 0 of port B. The #bit pre-processor directive is used to create a convenient way to access port B, bit 0.
#bit i d = x . y i d is a valid C identifier x is a C variable or constant y is a constant 0-7 The 1-bit variable is placed in memory at byte x, bit y. This is useful for referring to bits in special function registers.

105

We will also experiment with a type qualifier called typedef. Suppose I have a personal desire to refer to a 1-bit variable as a "bit" as opposed to the CCS term inti. I can use the typedef qualifier to satisfy my desire. [type-qualifier] [type-specifier] [declarator]
typedef inti bit; //use bit in place of inti

This will help you understand what the typedef qualifier does when you see it in code that you encounter.

////bit level demo //// uses- time delay via #include <16F818.h> #use delay(internal=4mhz) typedef inti bit; #bit signal = 0x06.0 bit flag;

Csl6 function call //clock oscillator internal, frequency //use bit instead of inti //port B, bit 0 //bit type variable called flag //delay function

del ( ) {
delay_ms( 500) ; return;

I'
//delay 500 milliseconds

} main() {
output_b (0x00); do //clear port B

{
flag = signal del(); flag = signal del(); 1; = flag; 0; = flag; //set flag //show flag //call delay function //clear flag //show flag //call delay function

}
while (TRUE);

BIT MANIPULATION USING BITWISE OPERATORS


The bitwise operators are:
Left shift Right shift <<= Left shift assignment >>= Right shift assignment & Bitwise AND Bitwise exclusive OR Bitwise inclusive OR &= Bitwise AND assignment < A= Bitwise exclusive OR assignment |= Bitwise inclusive OR assignment

Logical operations and shifting bytes sideways may be used to change specific bits in a register'* or variable. These methods are useful when two or more bits in a byte must be changed.

Shift Bits Right or Left


Bits in a register or variable may be shifted right or left one bit position at a time. Each time a shift occurs, the bit position which is vacated is filled with a 0.
Left shift Right shift

A shift operator is accompanied by an argument which tells the compiler how many positions to, shift. For example:
new = (high4); //variable new becomes variable high shifted // left 4 times

We can try out the shift bit manipulation operators by doing the following:

107

////shift demo using bitwise operator #include <16F818.h> #fuses INTRC_IO main()

Csl7a

{
byte datal; byte data2; datal = OblOlOlOlO; data2 = (datal3); output_b (data2); while (TRUE); //declare datal //declare data2 //declare bit pattern //shift right 3 times, fill 3 ms bits // with 0's //display result via port B LEDs //circle, always

} The result is 00010101.

Change Specific Bit To "1"


OR with an 8-bit binary number which is all zeros except the bit to be changed to a "1" OR with "0" leaves bit unchanged OR with "1" results in "1"

Change Specific Bit To "0"


AND with an 8-bit binary number which is all ones except the bit to be changed to a "0" AND with "1" leaves bit unchanged AND with "0" results in "0" (called masking)

Change Specific Bit To It's Complement


0 to 1 and 1 to 0 using exclusive OR XOR with an 8-bit binary number which is all zeros except the bit to be changed to its complement (Exclusive OR) XOR with "1" changes bit to complement XOR with "0" results in no change

We can experiment with the logic bit manipulation instructions by playing with bit 5 (arbitrary choice) in a variable.
Make it a "1" uging inclusive OR with 0010 0000 ////inclusive OR demo using bitwise #include <16F818.h> #fuses INTRC_IO main() operator Csl7b

{
byte datal; byte data2; datal = ObOOOOOOOO; data2 = (datal|ObOOlOOOOO ) ; output__b (data2); while (TRUE); //declare datal //declare data2 //declare bit pattern //inclusive OR, make bit 5 a "I" //display result via port B LEDs //circle, always

Make it a "0" using AND with ////AND demo using bitwise operator #include <16F818.h> #fuses INTRC_IO main ( )

101 1111
Csl7c

{
byte datal; byte data2; datal = ObOOlOllll; data2 = (datal&ObllOlllll) ; output_b (data2); while (TRUE); //declare datal //declare data2 //declare bit pattern //AND, make bit 5 a "0" //display result via port B LEDs //circle, always

109

Change it using exclusive OR with 0010 0000 (complement or invert)


////exclusive OR demo using bitwise operator #include <16F818.h> #fuses INTRC_IO main() Csl7d

{
byte datal; byte data2; datal = ObOOOOOOOO; data2 = (datal"ObOOlOOOOO); output_b (data2); while (TRUE); //declare datal //declare data2 //declare bit pattern //exclusive OR, complement bit 5 //display result via port B LEDs //circle, always

GOTO
The goto statement is used as an unconditional jump to code located somewhere else,
goto label;

label: statement;

Execution branches to location identified by label within the same function.


if (conditionl) goto labell; else statement;

labell: statement;

If you want the label to appear on a line by itself, you can place a semicolon after the label. A semicolon by itself is a null statement (docs nothing).
label: ; statement;

C purists don't like the use of goto statements and go all out to avoid using them.

In case you just gotta goto:

+5VDC

All LEDs Off

////simple goto demo /include <16F818.h> #fuses INTRC_IO main ( ) { output__b (0x00); if (input(PIN_A2)) goto labell; else output_b (ObOOOOOOOl); goto end; labell: ; output_b (ObOOOOO100); end: ; while (TRUE);

Cs 18

//clear port B //high?

//indicate port A, pin 2 switch closed

//indicate port A, pin 2 switch open //circle, always


1

A goto cannot jump outside a function and care should be taken to be sure an endless loop is not created.

FUNCTION LIBRARY
I would encourage you to start a function library. This can include functions you w r rite to do various tasks and functions you find in magazine articles, on the internet news groups or wher ever. The functions can be saved and stored as text files for future use. This saves reinventing them each time they are needed. Putting reusable functions into include files can be helpful. Note that if a file with several func tions is "included" and one (or more) of the functions is never called by the program, the compil er will not include it in the object file and it will not be programmed into the microcontroller.

CUT AND PASTE


Cutting and pasting blocks of text is a very convenient method for creating new programs using portions of existing ones.

112

TALKING TO A PIC MICROCONTROLLER WITH A PC VIA A WINDOWS TERMINAL PROGRAM


Having the ability to talk to a PIC microcontroller-controlled black box using a personal com puter (PC) running a terminal program under Windows (tm) opens up an area of interesting possibilities. The PC can: Send data or a command. Receive status information. Receive data - display data on-screen, print data, save data as a file, massage data with a spreadsheet program, graph data, do math manipulation, etc. Many PC's running Windows 95 and later have a built-in terminal program called "HyperTerminal" (tm). If HyperTerminal is not installed on your PC or you do not have the ver sion named "Private Edition", you should download a copy from Hilgraeve at hilgraeve.com. At this writing, HyperTerminal Private Edition (tm) is available at no charge for personal use. HyperTerminal does not have a "Clear Screen" capability. It you start out using HyperTerminal, you will soon recognize the value of the Clear Screen feature and download HyperTerminal Private Edition. From here on, I will simply use the name "HyperTerminal" What is a terminal? The term comes from the days of the teletype when a terminal looked like a typewrriter and was used to send and receive messages. For our purposes, HyperTerminal allows sending or receiving single ASCII characters, strings of ASCII charactcrs, or text files. HyperTerminal can easily set up a serial port such as COM2 and use it to communicate with a PIC microcontroller-based black box. It is not necessary to learn a programming language for the PC although doing so will enable you to create more sophisticated applications. The PC provides the on-screen user interface, file storage on disk, and printer plus data manipulation, graphing and display capabilities. The PIC microcontroller-based black box provides the controller, data acquisition system, instrument or whatever device you want to dream up.

113

"U-TURN" EXPERIMENT
The experiment that follows is an easy way to become familiar with terminal programs and to get a feel for how the PC end of things will work when connected to a PIC microcontroller-con trolled black box. You will need an ultra-simple piece of test equipment made from a 9-pin female D-subminiature solder cup connector and a short piece of wire. The wire connects pins 2 and 3.

9-pin Female D-subminiature Solder Cup Connector

The objective is to send stuff out a PC serial port on the transmit data (TD) line and to have it make a U-turn and come right back in on the receive data line (RD) of the same port. We will discuss hardware later. Now we will work toward understanding the HyperTerminal program and what can be done with it. The first objective is to open HyperTerminal and select a group of settings which will work for our applications, and save the file (settings) so we don't have to go through the setup procedure each time we wrant to do something. The second objective is to use the "U-turn" connector and send and receive a single character at a time.

114

We will create a communications setup file for use in our experiments. Open HyperTerminal: Start>All Programs>wherever it is>HyperTerminal Private Edition. The Connection Description dialog box will appear. Create a file name. Choose an icon, but you will not need it. Click OK. The Connect To dialog box appears. Select the serial port you wish to use (usually COM2). Connect using: COM2. Click OK. The COM2 Properties dialog box appears. Select the following: Bits per second: Data bits: Parity: Stop bits: Flow control: Click Apply. Click OK. ' The "filename" - HyperTerminal window is open. View>Font. Font: Courier or Courier New. The courier font is a monospace font. Font Style: Regular Size: 10 File>Properties The "filename" Properties dialog box appears. Click on the Settings tab. Emulation: ANSI The remaining settings should be the default settings. Click OK. File>Save. 4800 8 None 1 None

115

Strike a letter key. Nothing happens. The screen displays characters received, NOT characters sent. Turn off your computer. Install the "U-turn" connector at the serial port you are going to use. Generally this will be COM 2 as most systems have the mouse connected to COM 1. Turn on your computer. Open the settings file you just created in HyperTerminal. The HyperTerminal window should now be open, blank, and a cursor should be blinking in the upper left hand cor ner. Now type any character. The character will appear on-screen w^here the cursor was. The character displayed is actually the character received by the terminal program. If you type the letter "a", it will be transmitted out the COM 2 serial port on the TD line, make a U-turn, come back in the same serial port on the RD line and will be displayed on the screen. Note that the character sent is not displayed, the character received is. They happen to be the same in this case because of the U-turn. Three ASCII control characters are useful for controlling the placement of ASCII alphanumeric characters on the screen of the PC as they are received. This is important because we want the information to be readable and also because we will want data to be formatted to be saved as a useful text file. The three ASCII control characters are:

As an example, a carriage return is sent when the control and "m" keys are pressed simultane ously (control m). As you probably already know, carriage return causes placement of characters on the screen to move to the extreme left side. Line feed causes characters to be placed on the next line down the screen. Horizontal tab means tab over to the right. Theses terms come from the teletype days. Try experimenting with the first three control characters to get a feel for how they control place ment of the characters on-screen (formatting). To clear the screen, Edit>Clear Screen. For our PIC microcontroller-based experiments, the microcontroller will send data to the PC where it will appear on the HyperTerminal screen. Examples appear in the Strings and Math And Manipulating Numbers chapters.

116

PC-TO-PC "2-LANE HIGHWAY" EXPERIMENT


If you have two PCs available, you might like to do the following experiment to learn more about serial communication between twro PCs, both running HyperTerminal. A cable wrill be required to conncct the two serial ports. The simplest possible cable which will work consists of two data lines (one for each direction) and a ground line.

Cable Assembly

Notice that the transmit data (TD) line on computer 1 is connected to the receive data (RD) line on computer 2 and visa versa. You can easily make your own cable assembly using twro 9-pin female D-subminiature connectors and three lengths of wire. Keep the cable as short as possible (8 feet works for me). Both computers must communicate using the same settings for baud rate, etc. You can start by using the settings used previously in the setup examples.
Baud rate Data bits Parity Stop bits Flow control 4800 8 None 1 None

The objective is to establish bi-directional communication between two computers. We will assume both computers are running HyperTerminal. To establish bi-directional communication, connect the computers via the serial cable, turn both of them on and bring up HyperTerminal with your settings file in each. When you have the HyperTerminal window open in each computer, type a character in one of the computers. It will appear on the screen of the other computer. Now do the reverse. After you have played a little, clean off the screen in each computer by using Edit>Clear Screen.

117

PC/PIC MICROCONTROLLER
The hardware side of 2-way communications between a PIC microcontroller and a PC will be described next.

PC Baud Rates
Baud rate is defined as the number of bits transmitted per second. The baud rates available for serial communication via a PC using a terminal program are:

Baud Rates

118

RS-232 INTERFACE FOR A PIC MICROCONTROLLER


My objective here is to give you just enough information about RS-232 to make it possible to build a simple hardware interface between a PC1 serial port and a PIC microcontroller. A MAX233 RS-232 converter IC from MAXIM Dallas will be used to develop the 9 volts or so required to transfer data per the RS-232 standard. Among other things, an RS-232 converter chip is an inverter. There is an RS-232 converter chip inside the PC which inverts data going both ways. It is desirable to use one at the PIC microcontroller end too so that everything comes out right (see diagram on following page).

RS-232 CONVERTER

119

The connections between the PC, cable, RS-232 converter and PIC microcontroller are:

RS-232 CONVERTER

Note that only the wrircs used between the PC and RS-232 converter board in a particular experi ment are shown in the drawings that follow in this book. The third wire in the cable described in the "2-lane highwray" experiment will not interfere. The pin-functions for PC RS-232 serial connectors of interest here are:

Function Transmitted data (TD) Received data (RD) Common * Shown in this book

9-pin*
3 2 5

25-pin
2 3 7

The cable is the same one used for the PC-to-PC experiments. Note that transmit on one end goes to receive on the other end. To test your RS-232 converter, use a wire to connect the PIC microcontroller transmit and receive terminals (Rlout and Tlin) on the converter board. With the converter board connected to the PC via cable, a character sent using the PC terminal program will (should) appear on screen as was the case with the "U-turn'1 experiment. Up to this point, we have discussed 2-way communication between a PC and a PIC microcon troller. The RS-232 converter is designed for 2-way communication. The experiments which follow are 1-way with the microcontroller transmitting information to the PC.

120

PIC MICROCONTROLLER-TO-PC SERIAL COMMUNICATION


Next, we will get a PIC microcontroller to talk to a PC. We'll sec if the listener (PC) understood w'hat the talker (PIC microcontroller) said. The circuit for the experiments is:
RS-232 CONVERTER

SEND

RECEIVE

The PIC16F818 uses port A, bit 1 to transmit. For the purposes of this discussion, it is assumed that you will be using two PC's, one to develop code and program it into the PIC microcontroller using the ICD programmer/lCSP method and the other to display the results transmitted to it via RS-232. The procedure for firing-up the hardware and running the first example program is: 1) Powrer-up the PC running the CCS compiler and the device programmer. 2) Power-up the ICD programmer, the PIC microcontroller board and the RS-232 converter board. 3) Program the PIC 16F818. 4) Send switch open. 5) Bring the PIC 16F818 out of reset. 6) Power-up the PC that will receive the RS-232 communications from the PIC16F818. 7) Set up the HyperTerminal program as in previous examples (4800 baud). 8) Close send switch. 9) The character or string in the coming experiments should appear on the screen of the PC. To run the second and subsequent examples, life gets simpler. 1) 2) 3) 4) 5) 6) 7) 8) Clear screen on the display PC. Send switch open. Hold the PIC16F818 in reset. Import the next .cof file. Program the device. Release the PIC 16F818 from reset. Close send switch. Look at the result on the display PC screen.

121

FORMATTING PIC MICROCONTROLLER DATA ON A PC SCREEN


In the next chapter, you will learn about the printfQ built-in function which may be used to send ASCII alphanumeric characters to a PC for display on-screen. Three printf() escape sequences are useful for controlling the placement of ASCII alphanumeric characters on the screen of the PC as they are received. This is important because we want the information to be readable and also because we will want data to be formatted to be saved as a useful text file. The three printfQ escape sequences are:

New line Carriage return Horizontal tab

/n /r /t

(line feed)

As you probably already know, carriage return causes placement of characters on the screen to move to the extreme left side. Line feed causes characters to be placed on the next line down the screcn. Horizontal tab means tab over to the right. Theses terms come from the teletype days. The binary codes for these functions must be built into PIC microcontroller code and sent to the PC so that the data displayed will make sense to humans. Sample programs will illustrate how the printf() escape sequences work.
////escape sequence demo 1 /include <16F818.h> #use delay (internal = 4mhz) #use rs232 (baud = 4800, xmit = PINAl) main() Csl9a

{
while (input(PIN A2)==l); putc ('s'); printf ("C what happens!"); while (TRUE); //go low? wait for ready switch to close //start here on-screen //send ASCII character string out a // serial port

} Result: sC wrhat happens! Notice that the "s" and "C" are run together. In the next example, we will use the \n escape sequence to put "C what happens" on a new line. Notice, also, that the #use delay() and #use rs232() built-in functions are needed. As used here, putcQ outputs a single ASCII character and printf() outputs a string of ASCII characters. They are built-in functions. Single quotes are used to indicate a single character which is defined in the putc() function. Quotation marks arc used to indicate a string of charac ters w'hich is defined in the printf() function.

122

////escape sequence demo 2 /include <16F818.h> #use delay {internal = 4mhz) #use rs232 (baud = 4800, xmit = PIN A1) main()

Cs 19b

{
while (input(PINA2)==1); putc ('s'); printf ("\n"); printf ("C what happens!"); while (TRUE); //go low? wait for ready switch to close //start here on-screen //new line //send ASCII character string out a // serial port

Result:

C what happens! "C what happens!" is on a new line with a blank space one character wide. Adding a carriage return escape sequence will cure that problem.
////escape sequence demo 3 /include <16F818.h> /use delay (internal = 4mhz) /use rs232 (baud = 4800, xmit main() Csl9c

PIN_A1)

{
while (input(PIN_A2)==1); putc ('s'); printf ("\n\r"); printf ("C what happens!"); while (TRUE); //go low? wait for ready switch to close //start here on-screen //new line, carriage return //send ASCII character string out a // serial port

Result: s C what happens! That's better! Notice that the \n and \r are together (no comma).

123

Now let's try out the horizontal tab escape sequence.


////escape sequence demo 4 /include <16F818.h> #use delay (internal = 4mhz) #use rs232 (baud = 4800, xmit = PIN A1) main()

Csl9d

{
while (input(PIN_A2)==1); putc (s'); printf ("\n\r\t"); printf ("C what happens!"); while (TRUE); //go low? wait for ready switch to close //start here on-screen //new line, carriage return, horiz tab //send ASCII character string out a // serial port

Result: s C wrhat happens!

124

STRINGS
A "string" is a group of characters. C does not have a string data type (string of characters). String data is stored as one character per element in an array called a "string array". A string literal is enclosed in quotation marks. A string terminator tells the C compiler where the endvof the string is. It is called the null char acter or null zero and is added automatically by the compiler. You can't see it, but it is there. String length is the number of characters in the string including spaces and the null character. The null character must be included in the count even though it is not visible. Examples are:
char msg[6] = "error" error has 5 characters Adding room for the null character makes 6 char alarm[15] = "smoke detected"; smoke = 5 characters space = 1 character detected = 8 characters Add 1 to make room for the null character Total = 15

The next example program sends an ASCII character to a PC via the serial port. This example is nearly the same as what was done in the previous chapter, but now the emphasis is on explaining strings.

SEND

RS-232 CONVERTER

RECEIVE

125

////character demo - rs232 /include <16F818.h> #use delay (internal = 4mhz) #use rs232 (baud = 4800, xmit = PIN Al) main()

Cs20a

{
while (input(PIN_A2)==1); putc ('a'); while (TRUE); //go low? wait for ready switch to close //send 'a' out a serial port

} Power-up with the switch open. When you are ready to send, close the switch. Sending data some time after power-up allows time for the serial connection to become stable. Notice that the #use delay() and #use rs232() built-in functions are needed. The next example program sends an ASCII character string to a PC via the serial port. The hardware and procedure are the same.

////string demo - rs232 /include <16F818.h> /use delay (internal = 4mhz) /use rs232 (baud = 4800, xmit = PIN_A1) main( )

Cs20b

{
while (input(PINA2)==1); printf ("C what happens!"); while (TRUE); //go low? wait for ready switch to close //send ASCII character string out a // serial port

This is a short introduction to strings. Manipulation of strings can become quite involved. The CCS compiler has 22 built-in functions for working with strings. For now, it is sufficient to be aware that these possibilities exist.

126

ARRAYS
Arrays have "elements" and all of the elements are the same data type. Define array:
data type const array name [size]; number of elements in array modifier or qualifier (if needed)

"const" is the constant qualifier. Use of const here will cause the constants to be placed in pro gram memory (ROM). Again, the elements in an array are all of one data type. The data type is specified in the array definition. Data types have corresponding array types. As an example, we can define an integer array, which we will call i a, and the initial values:
int const i_a[4] = {1, 2, 3, 4};

4 elements

Note that the order is int const, not the reverse as in the CCS manual, i a[3] is the third element of the array which has a value of 4. The array name is the index to the first clement in the array. We can also declare an index to the array which will be useful in pointing to all elements in the array:
int pi_a; //declare index

The compiler will know that pi_a is an index because when it is used, it is associated with the name of the array.

127

With an array and an index, we can: Step through the array using the index. Point to the nth element in the array. Add an offset to the index. Examples will serve to illustrate. The index may be incremented by:
pi_a++ //increments index pi_a to next element of array

We can step through array elements using this technique.

////array demo Cs2 la //// uses time delay via function call /include <16F818.h> #use delay(internal=4mhz) //clock oscillator internal, frequency del ( ) //delay function

{
delay jus(500); return; //delay 500 milliseconds

}
main()

{
int const vals[4] = {0,1,2,3}; int pvals = 0; int i; output b (0x00); for (i=0; i<=3; i++) //declare integer //index to array //declare integer //initialize port //display 4 array array, initialize i, used for counting B values

{
output_b (vals[pvals]); del(); pvals++; //display data pointed to by index via // port B LEDs //call delay function //increment index to array //circle, always

}
while (TRUE);

} Notice that all the variables used in main() are defined ahead of the first instruction to be exe cuted output b (0x00);. If you fail to do this, you will get more than a few error messages from the compiler. Notice, also, that the compiler learns that pvals is an index by observing that it is enclosed in [ J when first encountered.

128

We can extract the nth element from an array.


arrayname[n]

////array demo - extract nth element /include <16F818.h> /fuses INTRCIO main()

Cs2 lb

{
int const vals[4] = {0,1,2,3}; outputb (0x00); outputb (vals[3]); while (TRUE); //declare integer array, initialize //initialize port B //display data pointed to by index via // port B LEDs //circle, always

129

'

An offset may be added to or subtracted from the index.


int offset; //define offset

Later in in the program:


offset = 2; //offset 2 from current index value //offset added to index

outputb (i_a[pi_a + offset]);

If your code requires retrieving the 4th element (remember that "0" counts) in the array (contains "3" in this example), because the result of an operation is 3, make offset = 3 and proceed. This assumes that the pi_a = 0 at the time operation begins.
again: outputb (i a[pi_a + offset]); //offset added to index

////array demo = add offset to index /include <16F818.h> /fuses INTRC 10 main()

Cs21c

{
int const vals[4] = {0,1,2,3}; //declare integer array, initialize int pvals = 0; //index to array int offset; //declare offset outputb (0x00); //initialize port B offset = 2; //value in offset outputb (vals[pvals + offset]); //display data pointed to by index via // port B LEDs while (TRUE); //circle, always

LOOKUP TABLES
A lookup table (array in C) may be used to convert one code to another. Let's say we want to convert numbers ranging from 0 to 9 to 7-segment signals to drive a display.

7-SEGMENT Number CODE 0 1 2 3 4 5 6 7 8 9 0x3F 06 5B 4F 66 6D 7D 07 7F 6F

DPgfe dcba 0011 0000 0101 0100 0110 0110 0111 0000 0111 0110 1111 0110 1011 1111 0110 1101 1101 0111 1111 1111

Note: Each port A line must be pulled up to +5 V via a 10 K resistor.

The proper 7-segment code may be pulled from an array of constants by adding an offset to the array index. The offset is the number we want to display. The 7-segment binary code for the number is stored as that array element.

131

For demonstration purposes, we will make the offset = 2. The 7-segment equivalent bit pattern will be accessed in the table/array and "2" will be displayed.

////array demo = 7-segment LED display /include <16F818.h> /fuses INTRC 10 main ( )

Cs21d

{
byte const nums[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
int pnums = 0; int offset; output^b (0x00); offset = 2; output_b (nums[pnums + offset]) while (TRUE); //declare integer array, initialize //index to array //declare offset //initialize port B //value in offset //display data pointed to by index via // port B LEDs //circle, always

} The processor adds the number to the array index wrhere it will be used as the offset to find the 7-segment code which is then used to drive the display. Since this program uses all 8 bits of port B, you will need to: Program the device in a device programmer followed by putting it in the socket on the 7-segment circuit board. Or Provide ICSP connections on your 7-segment circuit board in such a way as to allow programming followed by connecting pins RB7 and RB6 to the corresponding display segments. Run the program. Examine the 7-segment display to see if the program worked. In an application, the number would result from whatever is going on and would be equated to offset ahead of this code. This code would then display the number.

132

STRUCTURES
Structures are similar to arrays. Arrays have "elements" and all of the elements are the same data type. Structures have "members". Members may be all of one data type, or a mixture of data types as we shall see by way of examples. A structure is a way to group data which is related, but (usually) of different types, Think record in a database program. Name and phone number. Customer, address, phone, accounts receivable balancc. Etc. First, a structure type must be defined. Think of the definition as a layout, or plan, or template for the structure type you are creating. An instance of a structure is a structure variable which contains members that are also variables. Think of this as an individual record in a database. Each instance must be named/declared. Wc will need to load variables into each instance. Wc will need to be able to access an individual variable in a specific instance. Let's get started. Structure type definition:
struct [structure tag]

{
member definition; member definition;

member definition;

} This definition defines a single structure type. This is the layout. To refer to the structure type definition, use the structure tag.

133

Instances of the structure (think individual records in a database) may be declared in two ways: 1) At the same time the structure is defined,
struct [structtag]

{
member definition; member definition;

member definition; } instl, inst2, inst3;

"instl" is the name of an individual instance. We can declare one instance or more. 2) Later in the program.
struct structag inst4, inst5, inst6;

As an example, let us create a simple database to help you keep track of the "stuff' that you store in your garage, basement and mini storage. Since you are a very organized person, you store your stuff in boxes of two sizes (large and small) and they are numbered. When looking for some critical stuff, you can look in your database to see which facility it is in. When wandering dow'n the aisle in your mini storage (you neatly organize your stuff) looking for something, you can look for the box with the correct number on it. This is not a good microcontroller application, obviously, but it is one you can relate to for learn ing purposes.
struct stuff

{
int num:5; //box number, 1 thru 512, stored in 5 bits char desc[40]; //description of the (good) stuff in the box // 40 bytes allocated for storage char loc[4]; //box location - gar, base, or mini // 4 bytes allocated for location int size:l; //box size, 1 = large, 0 = small, stored in 1 bit

} This is the structure type definition. The number following is the number of bits allocated to the field. This example has a variety of data types in it. I used one bit for box size. The "large" or "small" box sizes could be spelled out, but the database would take up more memory space. This also provides an example using bits which is more microcontroller oriented (vs. PC oriented). Members may be a single data type, or a mix of data types.

134

Next, wc will declare some instances,


struct stuff boxl, box2, box3;

Variables are loaded into members using the dot operator, the period. This is also known as the structure member operator.
structurevariablename.membername

The structure variable name is the instance. To load the box 1 instance:
box1.num = 1; boxl.desc = "high school diploma"; boxl.loc = "mini"; boxl.size =0;

// diploma fits in small box

To access data in a member, individual instance, we do the following: To access the description for box 1, we would refer to it as:
boxl.desc

A simple example program will illustrate how this w'orks. Wc will: Define the structure type containing two member definitions. One is an integer and the other is a character. Declare twro instances. Load data in the two instances. Display the data in one member of each instance.

135

////structure demo 1111 uses time delay via function /include <16F818.h> #use delay(internal=4mhz)

Cs22a
call //clock oscillator internal, frequency //delay function //delay 500 milliseconds

del ( ) {
delay_ms(50 0); return;

}
struct test //declare structure type, assign tag //declare data type //declare data type

{
int a; char b;

}
main()

{
struct test iteml, item2; iteml.a = 1; iteml.b = 'x'; item2.a = 2; item2.b = 'y'; do //declare 2 instances of structure // type test //load data - binary 00000001 //load data - ASCII character x // encoded as binary 01111000 //load data - binary 00000010 //load data - ASCII character y // encoded as binary 01111001

{
outputb (iteml.a); del(); output_b (item2.b); del(); //display data via port B LEDs //call delay function //display data via port B LEDs //call delay function

}
while (TRUE);

} The program displays item 1 .a (00000001), delays 500 milliseconds, displays item2.b (01111001, the binary encoded equivalent of ASCII y), delays 500 milliseconds, and repeats. The LEDs at bits 3, 4, 5, 6 will appear to blink together.

136

STRUCTURES AND PORTS - BIT FIELDS


A structure may be created with one instance and assigned to a port to enable: Writing bit patterns to the port using a name for each bit pattern. The name may be associated writh a condition or an operation to be performed by the hardware connected to the port. Convenient reference to individual or groups of bits for read or write. Logical manipulation of the port bits. Compare. Set one bit/pin equal to another, perhaps on a different port. Whatever you might dream up. Bits arc allocated from low' order up. The number following is the number of bits allocated to the field,
int bit4:l; int data:4;

To accomplish this: Create a structure type and assign tag. Assign the structure to a port. The structure is "overlayed on a port" - single instance. Use a tag to create predefined/named bit patterns to be sent to the port as a whole. Assign names to the individual pins. Write or read pins by name. This makes it possible to use logic, do comparisons, ctc.using pin names. Two examples follow. The first is designed for w'riting to the port as a whole as well as writing (or reading) individual bits/pins. The second is simpler and is designed for individual bit access only. Since built-in I/O functions arc not used, we must take care of the TRIS registers.

137

Cs22b ////structure overlayed on a port demo 1111 port as whole or individual bits 1111 uses time delay via function call /include <16F818.h> //clock oscillator internal, frequency #use delay(internal=4mhz) //delay function del ( )

{
delayjms(2000); return; //delay 2000 milliseconds

}
//declare structure type, assign tag //declare data type for bit //declare data type for bit //declare data type for bit TAG //declare data type for bit //declare data type for bits 4-7 //name for bits //port B address (overlays struct on B) 0x06 const"~ INITl = {1,0, 1,0, //initialize 1 00000101 0}; const INIT2 = {1,1,0,0, 15}; //initialize 2 11110011 (15 = 1111)

struct main( )

{
set_tris_b (0x00) ; do INITl; INIT2; //port B outputs

//display init byte 1 via port B LEDs //call delay function //display init byte 2 via port B LEDs //bit //bit //bit 0=0 1=0 2=1

=0
=

0;

del(); portb.c = 1 del(); portb. d= 1 del();

//bit 3=1

}
while (TRUE);

138

Following is an example of a bits only application (no using the structure to write to the port as a whole). Notice that a structure tag is not needed.

////structure overlayed on a port demo Cs22c //// bit identification only //// uses time delay via function call /include <16F818.h> #use delay(internal=4mhz) //clock oscillator internal, frequency del( ) //delay function

{
delay_ms(2000); return; //delay 2000 milliseconds

}
struct //declare structure type //declare data type //declare data type //declare data type //declare data type //declare data type //declare data type //declare data type //declare data type //name for bits //port B address for for for for for for for for bit bit bit bit bit bit bit bit 0 1 2 3 4 5 6 7

{
inti a i inti b i inti c l inti d l inti e i inti f i inti g i inti h i }portb; #byte portb = 0x06 main()

{
set_tris_b (0x00); outputb (ObOOOOOOll); del(); portb.a = 0; del(); portb.b = 0; del(); portb.c = 1; del(); portb.d = 1; del(); while (TRUE); //port B outputs //initialize port B //bit 0 = 0 //bit 1 = 0 //bit 2 = 1 //bit 3 = 1

139

MATH AND MANIPULATING NUMBERS


MATHEMATICAL OPERATORS
The most useful mathematical operators are (no surprise):
+ * / Addition Subtraction Multiplication Division

We will use them in examples. You may encounter:


% Modulus

Modulus produces the remainder from division. For example: 15 % 6 evaluates to 3. 15 divided by 6 yields a remainder of 3. To avoid confusion, note that the symbol % is also used to format variables in a string for print ing using the printfQ built-in function.

OPERATOR PRECEDENCE
Operator precedence is important. Rules determine which mathematical operation takes place first, ie. takes precedence over others. We will include the increment/decrement operators in this discussion.
Operator Precedence + + -* / % + 1 inc, dec 2 mult, div, modulus 3 add, subtr

1 has higher precedence than 2 which has higher precedence than 3. A subexpression in parentheses is evaluated first, regardless of the operators involved. ( ( ( ))) Evaluated from the innermost out.

If there are two or more operators having the same precedence, they are evaluated left to right.

140

Examples: x = 2 + 3 * 4 evaluates to 14 (multiplication first, subtraction second), x = (2 + 3) * 4 evaluates to 20 (inside parentheses first, multiplication second), x = (2 * (4 + (6 / 2 ))) evaluates to 14 (inside parentheses first, work outward). x = 4 * 5 / 2 * 5 evaluates to 50 (start left, move right).

HARDWARE BLOCK DIAGRAM FOR MATH EXPERIMENTS

SEND

ltS-232 CONVERTER

RECEIVE

141

DATA TYPE SELECTION CONSIDERATIONS


Data types should be appropriate for each value and may be mixed in a calculation as you will soon see in examples. Variable size must be matched to the need. For example, if the result of a calculation is 39203, 16 bits w'ill be needed (int 16). One data type may be printed as another if appropriate. Define a test number as an int 16 (16-bit binary number). Print it as a long unsigned integer.
////numbers demo /include <16F818.h> #use delay (internal = 4mhz) #use rs232 (baud = 4800, xmit = PIN_A1) main() Cs23a

{
intl6 tnum = OblOOllOOlOOlOOOll; //declare test number while (input(PIN_A2)==1); //go low? wait for ready switch to close printf ("%5Lu", tnum); //39203, 5 characters, long unsigned // integer while (TRUE);

} Result: 39203 Unsigned numbers are positive. My philosophy is: Don't mix data types in heavy math. Do it all in binary. Timers and A/D converters work in binary anyway. Use printf() options to format the result in the desired data type so that humans can relate.

142

FORMATTING VARIABLES SUCH AS MATH RESULTS FOR PRINTING


The result of a mathematical calculation can be printed in a variety of formats,
printf (string);

% is used in the string to indicate that a variable is to be formatted in a certain way.


%nt n is optional and may be: 1-9 to specify the number of characters. 01-09 to specify the number of characters and indicate leading O's. 1.9-9.9 to specify the number of characters and indicate floating point, t is the type and may be one of the following: u integer Lu long integer w unsigned integer with decimal point inserted n has two numbers first is total field width second is desired number of decimal places And several others - see CCS compiler manual, printf() function description.

In the printfQ argument, the % formatting codes come first in the order that the variables are to be printed. The variables come sccond in the same order. Note the placement of commas and quotation marks.
printf("%_, %__, %_", x, y, z);

The first %_ is for the x variable, etc. _ There may be one or more variables. The number of % and the number of variables must match. To output a % in a printf, use %%. Examples follow which will serve to illustrate.

143

Text such as "Shaft Speed" or "Temperature" may precede the variable to indicate what it is, followed by a space. Units such as "RPM" or "F" may be printed following data writh a space between as shown in the example.

////numbers with units demo /include <16F818.h> #use delay (internal = 4mhz) #use rs232 (baud = 4800, xmit = PINAl) main()

Cs23b

{
intl6 tnum = OblOOllOOlOOlOOOll; //declare test number while (input(PIN_A2)==1); //go low? waitvfor ready switch to close printf ("text %5Lu units", tnum); //39203, 5 characters, long unsigned // integer while (TRUE);

} Notice the blank or space ahead of "units". This serves to separate "units" from the number. Result: text 39203 units The stuff inside the quotes gets printed verbatim (including spaces) except for the % and accom panying formatting characters. The variable whose name follows the comma is printed where the formatting characters are, per the instructions provided by the formatting characters.

144

Division example:
////division demo /include <16F818.h> #use delay (internal = 4mhz) #use rs232 (baud = 4800, xmit = PIN Al) main() Cs23c

{
intl6 num = 40000; int8 denom = 60; intl6 result; result = num/denom; while (input(PINA2)==1); printf ("%Lu", result); while (TRUE); //declare numerator //declare denominator //declare result //division //go low? wait for ready switch to close //666, 3 characters, long unsigned // integer

Result:
666

When the C compiler performs division this way, the result is a whole number (no remainder). Second division example:
////division demo - decimal point /include <16F818.h> /use delay (internal = 4mhz) /use rs232 (baud = 4800, xmit = PIN Al) main ( ) Cs23d

{
intl6 num = 40000; int8 denom = 60; int32 result; result = num/denom; while (input(PIN A2)==1); printf ("%3.2w", result); while (TRUE); //declare numerator //declare denominator //declare result //division //go low? wait for ready switch to close //666, 3 characters, long unsigned // integer

Result:

6.66
An int32 32-bit data type is required to hold the result. Some simple math examples appear in the following chapter.

145

PASSING VARIABLES
Variables are either global or local. Global variables are defined outside a function and can be used by any function. Local variables are defined inside a function (after an opening brace) and used within a function (before the corresponding closing brace). It is considered good practice to use local variables as much as possible so you can control access to them. If a function needs to use another function's local variable, that variable can be passed to the function that needs it. Access to local variables is controlled, a good thing.

PASSING ARGUMENTS
When passing variables from one function to another, the terms variable, argument and parame ter mean essentially the same thing. C programmers say that an argument is passed from the first function to the second function and that the second function receives a parameter from the first. A value is returned to the first (calling) function. This helps make understanding more difficult. Variables may be passed using a method called passing by value, sometimes called passing by copy. The copy of the value of the variable is passed, not the variable itself.

////passing variables demo /include <16F818.h> /fuses INTRC_IO sub(int datal)

Cs24a

//"subroutine" receives value of datal //divides datal by 2 //display result via port B LEDs

{
datal = datal / 2; output b (datal); return; main()

{
int datal; datal = 4; sub(datal); while (TRUE); //declare datal variable //initialize datal variable //value of datal is passed //circle, always

} Notice that the data type is in the receiving function's argument list.

146

In this example, the result is sent to the LEDs for display by the "subroutine". RETURNING VALUES
In the following example, two values are passed to a "subroutine" function w'here they are multi plied together and the resulting value is returned to the calling function ( main() ) where it is sent to LEDs for display. Notice that the data types are in the "subroutine" function's argument list.

////returning value demo /include <16F818.h> /fuses INTRC 10 sub(int datal, int data2)

Cs24b

//"subroutine" receives values of datal // and data2 //declare answer variable in sub //multiplies datal times data2

{
int subAnswer; subAnswer = datal * data2; return (subAnswer);

}
main()

{
int answer; int datal = 2; int data2 = 3; answer = sub(datal, data2); output_b (answer); while (TRUE); //declare answer variable in main //declare datal variable //declare data2 variable //values of datal and data2 are passed // answer is returned //display result via port B LEDs //circle, always

} The operation of the codc is explained in the comments. Notice that the variable answer is local in main() and subAnswer is local in sub(int datal, int data2).

PROTOTYPING FUNCTIONS
Using a function prototype allows a function to be defined before the actual code is used. If a prototype is used, the function is available for use with different arguments in other parts of the program as the need arises - ala built-in functions. In a prototype, the variables to be passed are defined.

147

To crcate a prototype, put an exact duplicate of the function's first line somewhere before
main( ) . function prototype; main() //ahead of main

{
function name (argument list); //calls function

}
function header " //same as prototype except no //function itself

{
function definition

} When the function is called, the correct data types must appear in the proper positions in the function argument list, ie. proper order as defined in the prototype.

////returning value demo - prototype /include <16F818.h> /fuses INTRCIO //function prototype sub(int datal, int data2); main()

Cs24c

{
int answer; int datal = 2; int data2 = 3; answer = sub(datal, data2); output b (answer); while (TRUE); //declare answer variable in main //declare datal variable //declare data2 variable //values of datal and data2 are passed // answer is returned //display result via port B LEDs //circle, always //function header - "subroutine" receives // values of datal and data2 //declare answer variable in sub //multiplies datal times data2

}
sub(int datal, int data2)

{
int subAnswer; subAnswer = datal * data2; return (subAnswer);

} A function prototype provides a hint to the compiler as to operation of a function, and allows a function to be defined before the actual code is used. For example, the function can be proto typed in line 5, it can be called on line 30, and the acUial function written on line 60. If you did not have the prototype on line 5, and you attempted to call the function on line 30, the compiler w'ould not know wrhat the function is because it has not been prototyped. Prototypes are not needed in small programs, but they are useful in large programs that include lots of linked libraries and include files. Typically, all the important user functions are proto typed in a .h header file.

148

OPERATORS
An operator is a symbol that instructs the C compiler to perform a specific operation on one or more operands.-An operand is an expression that the operand acts on. All the operators are listed here as a reference. Assignment operator
equal sign variable = expression; Assign the value of expression to variable = not used in math, only as assignment operator

Relational operators
== Equal to

> < >= <=


!= Not equal to Comparisons using relational operators

Logical operators
&& And I I Or ! Not

Increment and decrement


++ Increment Decrement Used with counters

149

Mathematical operators
+ -

*
/ % += - = * /= %=

Addition Subtraction Multiplication Division Modulus - gives division remainder Addition assignment Subtraction assignment Multiplication assignment Division assignment Modulus assignment

Bitwise operators
V .

<< Left shift Right shift = Left shift assignment = Right shift assignment & Bitwise AND Bitwise exclusive OR Bitwise inclusive OR &= Bitwise AND assignment A= Bitwise exclusive OR assignment |= Bitwise inclusive OR assignment

Pointer operators
& * Address-of operator Address unary Deferencing operator Indirection unary

Structure operators
Dot operator or structure member operator -> Structure pointer

Operators that dont fit in the categories


( ) Grouping , Sequential evaluation sizeof Size in bytes unary ?: Conditional expression

Not an operator
: Used in structure bit fields to indicate the number of bits allocated to a field

150

INTERRUPTS
When an event occurs which demands the microcontroller's attention, an interrupt may be gener ated which will cause the microcontroller to drop what it is doing, take care of the task that needs to be performed, and go back to what it was doing. When an interrupt occurs, the instruction currently being executed is completed. Then the PIC 16F818 jumps to address 0x004 in program memory and executes the instruction stored there. This program is called an interrupt service routine. An interrupt service routine may cause (as required) the microcontroller to first take notes on the status of the program it was exe cuting when the interrupt occurred so that it can find its place when it comes back. Then the interrupt service routine will handle the interrupt by doing whatever needs to be done. On com pletion, the routine will review its notes, set everything back to the way it was and take up the main program where it left off. The code needed to both preserve and restore the status of the program that was being executed when the interrupt occurred is created automatically by the compiler. This is called context saving. Interrupts are caused by events which must trigger a response from the microcontroller at the time they occur. For the PIC16F818, interrupts may come from one of several sources: External - outside the microcontroller via the RBO/INT pin. Timer 0 overflow from OxFF to 0x00. Port B logic level change on bits 7,6,5,4. A/D conversion complete. Data EEPROM write complete. And others. An interrupt flag is a bit in one of the Special Function Registers which, when set, indicates that a specific interrupt has occurred. The flag will remain set until it is eleared by software which is usually done in the interrupt service routine related to that specific interrupt. Interrupts may be enabled or disabled (masked) at two levels, global (all interrupts regardless of source) or specific (enable/disable specific interrupt sources).

151

EXTERNAL INTERRUPT SOURCES


Using external interrupts requires both hardware and software. Hardware must be provided to sense an event or condition which requires an interrupt to bring about some action. A signal must be generated and fed to the microcontroller INT pin. RBO/INT is a general puipose interrupt pin. The INT input is connected directly (internally) to a Schmitt trigger whose output is directed to the external interrupt tlag, so the TRJS register is not involved. The external interrupt is edge-triggered. The INTEDG bit in one of the control registers deter mines whether a rising or falling edge triggers an interrupt. The E X T I N T E D G E Q built-in function is used to make this selection.
ext_int_edge(edge) edge H_TO_L high to low transition at INT pin edge L_TO_H low to high transition at INT pin

An INT interrupt can be disabled using the INT enable (INTE) flag so that interrupts will be ignored until the INTE flag is set. INT interrupts can be serviced or ignored under software control. If an INT interrupt occurs, the INTF flag is set. The INTF flag must be cleared via software as part of the interrupt service routine before reenabling the interrupt or continuous interrupts will occur. The compiler generates code to take care of this automatically. Disable further interrupts (clear INTE flag). Service the interrupt. Clear INTF interrupt flag. Enable interrupts (set INTE flag).

INTERNAL INTERRUPT SOURCES Timer 0 Interrupt


Timer 0 interrupts occur on overflow from OxFF to 0x00.

Port B Interrupt On Change - Bits 7,6,5,4


An interrupt on logic level change on port B bits 7,6,5,4 sets the RB port change interrupt flag (RB1F). This interrupt is enabled/disabled via the RB port change interrupt enable bit (RBIE). Only port lines 7,6,5,4 configured as inputs are effected. The pin's value in input mode is compared with the old value latched in the last reading of port B. The "mismatches" of the pins arc OR'cd together to generate the RB1F interrupt. The interrupt may be cleared by: Disabling the interrupt by clearing the RBIE bit. Read port B, then clear the RBIF bit. This ends the "mismatch" condition and allows RBIF to be cleared.

152

The interrupt on change feature is recommended for wakeup on key depression operation and operations where port B is only used for the interrupt on change feature. Polling of port B is not recommended while using the interrupt on change feature. Reading the port messes up the mis match.

Interrupts Generated By Other Peripherals


Other peripherals such as a 16-bit timer/counter (timer 1 = TMR1), an A/D converter, a com parator (not present on the PIC16F818), etc. can generate interrupts specific to their operation. The CCS compiler includes a long list of peripheral interrupt handlers which are available for your use.

GLOBAL INTERRUPT ENABLE FLAG (GIE) v


The occurrence of an interrupt clears the global interrupt enable flag disabling further interrupts while the interrupt is being serviced. As the interrupt service routine is completed, code auto matically created by the compiler sets the global internipt flag enabling further interrupts.

RETURN FROM INTERRUPT


An inteiTupt service routine must end with a return from interrupt instruction which causes exe cution to resume where it left off. This instruction is created automatically by the compiler.

WHERE TO PUT THE INTERRUPT SERVICE ROUTINE IN PROGRAM MEMORY


The interrupt vector built into the PIC16F818 is 0x004. When an interrupt occurs, the interrupt vector points to program memory address 0x004 where the first instruction of the interrupt ser vice routine must be stored. The first instruction is placed there by the compiler. You don't have to think about it. How to create an interrupt service routine and where to place it in the code will be shown in examples in this chapter and the following one.

INTERRUPT LATENCY
When an interrupt occurs, there will be a delay (latency) before the interrupt service routine is executed. This delay will be 3 or 4 instruction cycles.

MULTIPLE EXTERNAL INTERRUPT SOURCES


Conveniently, the PIC 18 Series devices have three possible external interrupt pins and the interrupts may be prioritized (advanced topic). Interrupt flags indicate which INT pin caused the interrupt. A different software subroutine is needed for each interrupt source.

153

INTERRUPTS IN C
The things you need to know (short list) about w'riting code to handle interrupts in C follow':

Functions - Built-in
ENABLE INTERRUPT SQ D ISABLE IN TE RR UPTS() enab le inte rr upts (level) disa ble inte rr upts (level)

level is a constant which defines an interrupt source. These constants are found in the device .h file for the device you are using. EXT INT EDGE () ext _int_edge (e dge)

edge H_T0_L high to low transition at INT pin edge L TOH low to high transition St INT pin

Timer 0 interrupt functions - in Timing And Counting Using Timer 0 chapter.

Pre-processor Directives Used To Identify Interrupt Service Routines


#int_xxxx xxxx refers to the on-board peripheral which is the source of the interrupt.
Example: #int_AD //analog to digital conversion complete

Long list of peripheral interrupt directives appears in the CCS compiler manual in the #int_xxxx pre-processor directive description. A #int_xxxx directive in the code is followed immediately by the user-written interrupt service routine (a function).
Example: #int_ext i_serv() "( .... //external interrupt

//user interrupt service routine

} The #int xxxx directive causes the compiler to generate code that will take care of: Context saving. Clearing the interrupt Hag that triggered the interrupt. Return from interrupt. The #int_xxxx directive should be placed ahead of main( ) (see example).

154

EXAMPLE - EXTERNAL INTERRUPT


INT is edge sensitive. It will respond to a rising edge if the INTliDG bit (bit 6 in the option reg ister) is set, or to a falling edge if the INTliDG bit is clear. To generate an INT interrupt, the INT pin must detect the edge of a pulse. For demonstration purposes, this can be done using the negative-going output of the pulscr circuit described in Appendix A. The pulser output is normally logic I and the output is a negative-going pulse. We will set up to respond to the falling edge. When an INT interrupt occurs, the microcontroller: 1) Completes execution of the current instruction. 2) Tests the global interrupt enable Hag. If the flag is set, global interrupts are enabled. 3) Tests the INT interrupt enable flag. If the flag is j>et, INT interrupts are enabled and the microcontroller will begin the interrupt sequence. 4) If either interrupt enable Hag indicates "disabled", the microcontroller will continue whatever it was doing when the interrupt signal was received and ignore the interrupt. 5) Clears the global interrupt enable flag to prevent further interrupts (automatic on recognition of interrupt). 6) Saves the address of the next instruction on the stack (automatic). 7) Jumps to the address pointed to by the INT vector = 0x004. The C compiler automatically placcs the first instruction of the interrupt service routine at that address. There is a Schmitt trigger external interrupt (INT) input connected directly (internally) to port B, pin 0, so the TRIS register is not involved. The following test program used in conjunction with the pulser circuit described above will serve to demonstrate an INT interrupt.

+5VDC

155

Main . Program

LEDs Off

Interrupt Service Routine

Toggle Interrupt LED

Return From Interrupt

The program scans the status of the switch connected to port A, bit 0 and displays its status at port B, bit 1. Its operation serves as something to do for demonstration purposes while waiting for the interrupt to occur. The output is visible, so operation of the program can be verified by changing switch settings w'hile the program runs. Note that the rNT interrupt is enabled (essential). If an INT interrupt occurs, the microcontroller jumps to 0x004 where the interrupt service rou tine begins. The interrupt service routine toggles the interrupt indicator LED at port B, bit 2 indicating an interrupt has occurred and then returns to the main program. The port B pullups are turned off. The program calls for response to a falling edge on the INT line.

156

////external interrupt demo /include <16F818.h> /fuses INTRCfO /bit sw = 0x05.0 /bit sw_stat = 0x06.1 /INT_EXT i_serv()

ala pictl5.asm

Cs25

//port A, bit 0 - switch //port B, bit 1 - switch status LED //external interrupt //interrupt service function //toggle interrupt LED

{
output_toggle(PIN_B2);

}
main()

{
output_b (0x00); ext_int_edge (H_TO_L); clear_interrupt (INT_EXT); enable_interrupts (GLOBAL); enable interrupts (INT EXT); while (TRUE) {swstat = sw;} //port B bits 7-1 low //interrupt on falling edge //clear external interrupts //enable interrupts

//status LED = switch

} The return from interrupt code is generated by the compiler automatically. The level constants (such as INT EXT) used with the built-in interrupt functions may be found in the device .h file for the device you are using. Notice that i serv follows immediately after /int
ext

and ahead of main.

Program a PIC 16F818. Power-up your test circuit. Change the position of the switch on port A, bit 0 to confirm that the main program is running. Pulse the INT line several times and observe the result at the LED connected to port B, bit 2. Remember that an interrupt should not occur during a software timing loop as it will lengthen the loop by the time required to service that interrupt. The delay_cycles(), delay_us(), and delay_ms() built-in functions use timing loops created in software and do not use a hardware timer such as timer 0 (TMR0). Also, an interrupt which occurs while the PIC 16F818's timer is in use may or may not be serviced before the timer times out. The use of interrupts greatly enhances the power of the microcontroller. Interrupts may be peri odic, as determined by a real time clock, or may be related to an event such as a counter count ing down to 0 or a burglar tripping an alarm. The microcontroller does not have to go around and around in a loop waiting for these things to happen, so it can perform other useful tasks in the meantime.

157

TIMING AND COUNTING USING TIMER 0


DIGITAL OUTPUT WAVEFORMS
Digital output waveforms are easy to generate by writing ones and zeros to a port line. A posi tive going pulse of short or long duration may be output by initializing the line to 0, outputting a 1, using a software timing loop or hardware timer to measure the pulse duration, and then writ ing a 0 to the port line. Square waves are easy to generate as you know from some of the examples:

Delays Are Equal

A rectangular wave is produced if the delays are not equal. If the delays are changed each time around the loop, sweep frequencies or frequency-modulated signals may be generated. As an alternative, the PIC 16F818 timer 0 timer/counter may be used. An advantage is that the microcontroller is not tied up generating repetitive waveforms.

158

USING TIMER 0
PIC microcontrollers have an 8-bit (in most cases) timer/counter called Timer 0, or TMRO, timer 0 (CCS), or RTCC as it was called in the early days of PIC microcontrollers. Timer 0's features are: 8-bit. Read/write. 8-bit software programmable prcscaler. Internal or external clock. Edge-rising or falling (external clock). Increments. Interrupt on overflow from OxFF to 0x00 with flag output.

Timer 0 has an interrupt on overflow from OxFF to 0x00 and is capable of doing other tasks while timing/counting is going on. Three built-in functions are used to control timer 0: setup timer 0 {mode) where mode options are device dependent and include things like clock oscillator internal or external plus prescaler selection. set timerO (value) determines the initial value loaded into the counter. get timerO () reads the timer. Details are in the CCS compiler manual. The options for each built-in function are in the device .h file (P1C16F818) for the examples which follow. The clock source for timer 0 may be either the PIC16F818's internal instruction cycle clock or the T0CKI pin. An external clock may be an oscillator running (much) slower than the PIC 16F818's clock oscillator or it might be a source of pulses to be counted. The input is either fed directly to the timer/counter (bypassing the prescaler) or through an 8-bit software programmable prescaler.

Input

T0CKI

The prescaler value may be l-of-8 as determined by using the setup timer0() built-in function. All instructions which write to timer 0 will dear the prescaler.

159

If an external clock source is used with no prescaler, synchronization of the external clock input must lake place. This requires a short sampling procedure plus a delay after synchronization occurs and prior to the timer/counter being incremented. There are some requirements for an external clock signal. No Prescaler Input high for at least 2 Tosc. Input low for at least 2 Tosc. With Prescaler Input period of at least 4 Tosc divided by the prescaler value. Highs and lows must be of greater than 10 nanoseconds duration. Tosc is the period of the PIC16F818 clock oscillator. If there is a write to the timer/counter, incrementing is inhibited for the next 2 instruction cycles. This can be compensated for by adjusting the number loaded in the timer/counter. External clock pulses may be detected on their rising or falling edge (software selectable via the setup timerOQ function). The timer 0 outputs are: Reading timer 0 using the get_timerO() function. Interrupt on overflow from OxFF to 0x00. The timer is incremented by incoming pulses. When the count climbs through OxFF, the count starts over at 0x00. The timer may be incremented over and over if need be and the number of times the counter reaches a certain value may be counted using a file register as a counter.

PRESCALER
There is an 8-bit counter which may be used as a prescaler for timer 0. The prescaler divides the clock input by one-of-eight values which effectively reduces the frequency of the clock. The prescaler is used to divide the input by:
1 2 4 (bypass prescaler) (default)

16 32 64 128 256

The prcscaler assignment and ratio are determined by using the setup_timer0() function as will be demonstrated in examples which follow. W'hen the prescaler is assigned to timer 0 using the set timerOQ function, the prescaler will be cleared to prepare it for division of the input signal.

160

PUTTING TIMER 0 TO WORK Setting up timer 0


Assign prescaler value using the setup timcr()() function. Syntax: setup timer 0 {mode)

mode, in our examples, is two constants. The first defines the clock source. The second
defines the prescaler ratio. A prescaler ratio of 1 causes the prescaler to be bypassed. The two mode constants are or'ed together using the bitwise inclusive or operator | per the instructions in the CCS compiler manual. The mode constants may be found in the dcvice .h file.
setup_timer_0(RTCC_INTERNAL|RTCC DIV-256); //internal clock osc, //prescaler divides clock osc by 256

Starting timer 0
Write a value to timer 0 using the set_timer()() function.

Counter

How do we know timer 0 is doing something?


Successive reads of timer 0. Interrupt on overflow OxFF to 0x00. This is how we know the timer/counter is finished counting. The PIC 16F818 is free to do other things while timer 0 is doing it's thing. The timer 0 interrupt flag is the output.

Timer 0 will keep counting as long as:


It is not cleared or written to by a program function. The microcontroller is not reset.

Timer 0 must be reloaded after each overflow for repeating time intervals
If this is not done, the count will start at 0x00 each time.

Stopping timer 0
Can't - it just runs. Experiments follow which will illustrate the use of timer 0.

161

TIMER 0 EXPERIMENTS
For programs using timer 0, we will use:
#fuses intrc_io

and
setuposcillator(OSC_4MHz);

An external or the internal clock oscillator, whichever is used, is divided by 4 internally and becomes the instruction clock. For a 4 MHz clock oscillator, the internal instruction clock fre quency is 1 MHz. This is the frequency fed into the timer 0 prescaler.
*

Digital Output Waveform Using Timer 0 - Internal Clock


Use internal clock divided by 256. Blink an LED at fast rate - delay 8.2 milliseconds (view with oscilloscope).

Start Timer 0

162

Timer 0 is read continuously. When timer 0 increments to 32, the LED is toggled. The time interval is roughly 1 microsecond per internal clock cycle divided by 256 (prescaler) equals 256 microseconds per pulse into timer 0 times 32 = 8.2 milliseconds. The microcontroller is totally occupicd with this timing application when this method is used.

////timer/counter demo //// free running //// internal clock + 256 /include <16F818.h> /fuses INTRCIO main()

ala pict7.asm

Cs26a

{
setup oscillator(OSC_4MHZ); //4 MHz clock oscillator setup_timer_0(RTCC_INTERNAL|RTCCDIV 256); //internal clock osc, //prescaler divide clock osc by 256 output b (0x00); //all port B pins low do

{
outputtoggle(PIN_B0); //toggle port B, bit 0 set_timerO(0); //clear and start timer 0 while (get_timerO() < 32); //time < 32

}
while (TRUE);

Single Time Interval - Internal Clock


Use internal clock divided by 128 and file register counter. Timer 0 increments to OxFF and rolls over. Interrupt is generated and file register used as a counter is incremented. When count reaches 255, LED is turned off. Result is LED blinks once.

163

Main Program

Start Count

Interrupt Service Routine

The on-time is 1 microsecond/internal clock cycle x 128 x 256 x 256 = 8.4 seconds. Timer 0 is incremented 256 times to overflow. The variable "t" is incremented 255 times.

////timer/counter demo //// single time interval //// internal clock -j- 128 //// file register counter /include <16F818.h> /fuses INTRC_IO int t = 0; /INT_TIMER0 i_serv()

pictl6.asm

Cs26b

//declare t for counter //enable timer 0 interrupt //interrupt service function //increment counter //time interval completed? //LED off

{
t++; if (t==255) output_low(PIN_B0);

>
main()

{
//4 MHz clock oscillator setuposcillator(OSC_4MHZ); setup_timer_0(RTCC_INTERNAL|RTCC DIV_128); //internal clock osc, //prescaler divide clock osc by 128 //all port B pins low outputb (0x00); //clear timer 0 interrupts clear interrupt (INTTIMERO); enableinterrupts (GLOBAL); //enable interrupts enable_interrupts (INT_TIMER0); //clear and start timer 0 settimerO(0); //LED on outputhigh(PINBO); while (TRUE);

165

Free Running Mode - Internal Clock


We will need an external clock with a period of 0.1 second = 100 milliseconds for the example following this one. It makes sense to use a PIC microcontroller, don't you think? The time interval between toggle operations (period divided by 2) is roughly 1 microsecond per internal clock cycle times 2 (prescaler divide by 2) times 256 rollovers times 98 = 50,176 microseconds = 50 milliseconds = period -s- 2. Use internal clock divided by 2 and file register counter. Timer 0 increments to OxFF and rolls over. Interrupt is generated and file register used as a counter is incremented. When count reaches 98, port B, bit 0 is toggled. This occurs at one-half period intervals. Result is a square wave output with period = 100 milliseconds.

166

Main Program

Start Count

Interrupt Service Routine

////timer/counter demo I I I I free running 1111 internal clock + 2 1111 file register counter /include <16F818.h> /fuses INTRC 10 int t = 0; /INT_TIMERO i_serv()

Cs26c

//declare t for counter //enable timer 0 interrupt //interrupt service function //increment counter //time interval completed? //toggle port b, pin 0 //clear counter

{
t++; if (t==98)

{
output_toggle(PINBO); t = 0;

} }
main()

{
//4 MHz clock oscillator setup_oscillator(OSC 4MHZ); setup_timer_0(RTCC INTERNAL|RTCC DIV_2); //internal clock osc, //prescaler divide clock osc by 2 output_b (0x00); //all port B pins low clear interrupt (INT_TIMER0); //clear timer 0 interrupts enable_interrupts (GLOBAL); //enable interrupts enable interrupts (INT_TIMER0); set_timer0(0); //clear and start timer 0 while (TRUE);

To observe the square wave output on port B, pin 0, you will need an oscilloscope.

168

Single Time Interval - External Clock


Use external 0.1 second clock (another PIC microcontroller running the code in the previous example). Square wave input - increment counter on rising edge (low to high transition). Bypass prescaler. Blink an LED once. Note: It takes about 25.6 seconds for the LED to turn off, so wait patiently! 256 clock pulses are needed to overflow timer 0.

Main Program

L to H Rising Edge

Start Count

Interrupt Service Routine

169

////timer/counter demo ala pictl7.asm 1111 single time interval I I 1 1 external clock, bypass prescaler 1111 blink LED once /include <16F818.h> /fuses INTRC 10 /INT_TIMERO //enable timer 0 interrupt i_serv() //interrupt service function

Cs26d

{
output_low(PIN_BO); //LED off disable_interrupts (INT_TIMERO); //disable timer 0 interrupt

}
main( )

{
setup_oscillator(0SC4MHZ); //4 MHz clock oscillator setup_timer_0(RTCC_EXT L_T0_H|RTCC_DIV 1); //external clock osc, low to hi //prescaler bypassed output_b (0x00); //all port B pins low //clear timer 0 interrupts clear_interrupt (INT TIMERO); //enable interrupts enable_interrupts (GLOBAL); enable_interrupts (INTTIMERO); //clear and start timer 0 set_timerO(0); outputhigh(PINBO); //LED on while (TRUE);

Free Running Mode - Internal Clock


Use internal clock divided by 128. Output to port B, bit 0. Interrupts are used. The microcontroller is free to do other things when not servicing interrupts (not done in this example).

170

Main Program

Interrupt Service Routine Start Count

171

////timer/counter demo I I I I free running 1111 internal clock -j- 128 1111 10 counts to overflow /include <16F818.h> /fuses INTRC 10 /INT_TIMERO i_serv()

ala pict 20.asm

Cs26e

//enable timer 0 interrupt //interrupt service function //toggle LED //load timer 10 coynts (decimal) to // rollover

{
output toggle(PIN_B0); settimerO(Oxf6);

>
main()

{
setup_oscillator(0SC4MHZ); //4 MHz clock oscillator setup_timer_0(RTCC_INTERNAL|RTCC DIV_128); //internal clock osc, //prescaler divide clock osc by 128 output_b (0x00); //all port B pins low clear_interrupt (INT TIMER0); //clear timer 0 interrupts enable_interrupts (GLOBAL); //enable interrupts enable_interrupts (INTTIMERO); while (TRUE);

Run the program and look at port B, bit 0 with a scope. Examples:
Load 0xF6, prescaler + 128 (10 counts to overflow) l.usec x 128/count x 10 = 1.2 8 msec

This is the time between each HI/LO or LO/HI transition at the port line. The time to execute the interrupt service routine adds to this slightly.

172

Load 0x00, prescaler + 128 0x00 = 256 decimal Counts to overflow 128 ^.sec x 256 = 33 msec

Load 0x00, prescaler bypassed (-^1) 1 j.isec x 256 = 256+ (.isec (no allowance for program overhead)

Load 0x40, prescaler -4- 2 (192 counts to overflow) 1 (.isec x 192 x 2 = 384+ (.isec (no allowance for program overhead)

384+ usee

Counting Events (Pulses)


Use the pulser described in Appendix A as a source of negative-going pulses. The negativegoing pulses will increment the counter on the falling edge (high to low transition).

174

////event counting demo /include <16F818.h> /fuses INTRC_IO /byte portb = 0x06 /byte timerO = 0x01 main()

ala pict21.asm

Cs26f

// port B address

{
setup_timer 0(RTCCEXT_H_TO_L|RTCC_DIV_1); //external pulses, //prescaler 1 = bypass output_b (0x00); //all port B pins low set_timer0(0); //clear and start timer 0 while (input(PINA0)==0); //wait for switch to open portb = timerO; //port B = timer 0 while (TRUE);

1). Power-up with switch closed. Open switch - all LEDs should he off. 2). Power-up with switch closed. Pulse X (few) times. Open switch. LEDs display pulse count X.

GOING FURTHER
Wouldn't it be nice if our timer/counter could count beyond 255! A 16-bit timer counter known as Timer 1 (TMR1) is available in many of the PIC devices. It can count up to 65,535. TMR1 is usually accompanied by an 8-bit timer/counter called Timer 2 (TMR2) and a capture/compare/pulse width modulation module (CCP) which makes a lot of very useful appli cations relatively easy to implement. How-to information using assembly language is contained in our book entitled Time'n and Count'n.

175

ANALOG TO DIGITAL CONVERSION


Analog to digital (A/D) conversion is essential to using PIC microcontrollers because they can only process digital information. Analog signals, usually voltages from sensors, must be con verted to binary numbers digestible by the PIC microcontroller. As usual, this may be done by one of several methods with the usual cost/accuracy/resolution/PCB in2/etc. tradeoffs to be made.
V

The PIC16F818 has 5 pins which may (or may not) be used as A/D channels. These are port A, bits 4,3,2,1,0. The five analog inputs are multiplexed into one sample and hold circuit. The out put of the sample and hold is the input to a successive approximation converter The reference voltage may be the logic supply (5 volt for our example) to the PIC16F818 (range 0-5V) or an external reference via pins RA2 and RA3. If an external reference is used, only 4 A/D channels are available. The intricacies of using an external voltage reference are beyond the scope of this book. Important electrical specs are:

0 to 5V if Vre = 5V logic supply Vref 5V logic supply for this example Maximum source impedance 2.5K

vain

A pre-processor directive and four built-in functions are used to control the A/D conversion process: #dcvice with the chip option ADC=x determines whether the A/D conversion result is 8 or 10 bits. 8-bit mode is the default. setup adc {mode) where mode options are device dependent and include things like A/D off and conversion clock speed (conversion sample time). setup adc ports (value) determines which pins having analog capability are actually used for A/D in the application. set adc channel (chan) determines which A/D channel will be read next. read_adc ([mode]) controls the conversion proccss. Details are in the CCS compiler manual. The options for each built-in function are in the device .h file (PIC16F818) for the example which follows.

176

A simple example follows which uses one A/D channel (ANO) to measure the voltage on the wiper of a potentiometer, gives an 8-bit result, and displays the least significant 6 bits of the result via 6 LEDs. The voltage is measured once every 200 milliseconds.

+5VDC

177

////A/D test /include <16F818.h> /device ADC=8 /use delay (internal=4mhz) main()

Cs27
//A/D read returns 8 bits //clock oscillator internal, frequency

{
byte result; setup adc(ADC_CLOCK_DIV_32); setup_adc_ports (ANO); set_adc_channel (0); delay us(10 ) ; read do //declare variable result (a byte) //A/D - clock divided by 32 //use analog channel 0 //prepare to read analog channel 0 //short delay between select channel &

{
result = read_adc(); output_b (result); delay_ms(2 00); //start and read A/D //bits to port ^ //delay between reads

}
while (TRUE);

} For this example, notice: A pre-processor directive is used to specify that the result is to be 8 bits. 8-bit A/D is the default. It is specified here to make you aware of what is taking place. An 8-bit result provides 256 possibilities. A pre-processor directive is used to: - Select the internal clock oscillator - Select the frequency for time delay purposes. For A/D conversion, the clock oscillator frequency is divided by 32 as we are not in a hurry and doing so will avoid some timing issues. A pin used as an analog channel must be an input pin (taken care of by the compiler). A time delay is used between selecting the A/D channel to be read and the first reading of the A/D so as to conform to A/D use rules as spelled out in the PIC16F818 Data Sheet. Comments in the code provide the rest. Allow the devicc to come out of reset and observe the LEDs as you turn the potentiometer shaft. The count read from the A/D converter is displayed in binary (least significant 6 bits).

178

INSERTING ASSEMBLY CODE IN C CODE


For those of you who have experience with PIC assembly language, it may be useful to insert some assembly code in a C program that you are writing. The most common reason for doing this is to optimize a situation where timing is critical. If you have a really neat trick that you have created in assembly language and have not figured out a way to do it in C, or think it can't be done in C, there is a solution. Two pre-processor directives are used.
#asm #endasm

The code between the directives is treated as assembly code by the compiler. A very simple example follows:

////assembler in C program /include <16F818.h> /fuses INTRC_IO /byte portb = 0x06 main()

ala pictl.asm

Cs28

//port B address

{
set_tris_b (0x00); /asm movlw OxOf movwf portb /endasm while (TRUE); //port B outputs

//bit pattern to port B //circle, always

} Note that the portb identifier/label works in both C and assembly.

179

APPENDIX A - PULSER
It is easy to build a simple pulser circuit which provides both positive-going and negative-going pulse outputs. The circuit is built around a 74HC14 hex Schmitt trigger inverter IC and a single pole, single throw momentary contact toggle switch. The switch is spring-loaded to the normal ly closed position. Pushing the switch lever and releasing it results in one pulse being generated. Either the positive-going or negative-going output is connected to the PIC microcontroller cir cuit being tested as determined by the application. The debouncing circuit is followed by a resistor and capacitor which function as differentiator creating a narrow pulse of 10 or more microseconds duration. The circuit can be constructed on a solderless breadboard or constructed using a more permanent method of your choosing.

PULSER CIRCUIT

Contact Switch

74HC14

180

APPENDIX B - SOURCES
CCS, Inc.

P.O. Box 2452 Brookfield, WI 53008 Sales 262 522 6500 ext. 35 Tech Support 262 522 6500 ext. 32 FAX 262 522 6504 Web www.ccsinfo.com/picc email ccs@ccsinfo.com
Digi-Key

C Compilers Development Systems

701 Brooks Avenue South Thief River Falls, MN 56701-0677 Tel 800 344 4539 Web http://www.digikey.com
Jameco Electronics

Electronic Components PIC Microcontrollers

1355 Shoreway Road Belmont CA 94002-4100 Tel 800 831 4242 Fax 800 237 6948 Web http://www.jameco.com email info@jameco.com
Marlin P. Jones & Assoc Inc

Electronic Components PIC Microcontrollers

Electronic Components

P.O. Box 12685 Lake Park FL 33403 Tel 800 652 6733 Fax 800 432 9937 Order Online http://www.mpja.com email orders@mpja.com
Microchip Technology Inc.

2355 W. Chandler Blvd. Chandler AZ 85224 Tel 480 792 7200 Web http://www.microchip.com

PIC Microcontrollers PIC Programmer Development Systems In-Circuit Debuggers

181

APPENDIX C HEXADECIMAL NUMBERS


Hexadecimal Binary Decimal

0 1 2 3 4 5 6 7 8 9 A B C D E F

0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

APPENDIX D PROGRAM LISTINGS vs. PAGE NUMBER

Program Number Csla Cslb Cs2a Cs2b Cs2c Cs2d Cs2e Cs2f Cs3 Cs4 Cs5 Cs6 Cs7a Cs7b Cs8a Cs8b Cs9a Cs9b CslOa CslOb Csl 1 Csl2 Cs 13 Cs 14 Csl5 Cs 16 Csl7a Csl7b Csl7c Csl7d Csl8 Csl9a Csl9b Csl9c Csl9d

Page Number 34,73 74 75 77 78 79 80 81 83 85 87 88 90 91 92 93 95 96 97 97 99 101 101 103 105 106 108 109 109 110 112 122 123 123 124

Program Number Cs20a Cs20b Cs2 la Cs2 lb Cs2 lc Cs2 Id Cs22a Cs22b Cs22 c Cs23a Cs23b Cs23c Cs2 3d Cs2 4a Cs24b Cs2 4c Cs25 Cs26a Cs26b Cs26c Cs26d Cs26e Cs26f Cs2 7 Cs2 8

Page Number 126 126 128 129 130 132 136 138 139 142 144 145 145 146 147 148 157 163 165 168 170 172 175 178 179

183