C What Happens Ebook
C What Happens Ebook
C What Happens Ebook
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 [email protected] 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
34 34 34 34
36
36 36 36 36 38
40 41 42
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
55 56 57
58
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
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
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
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
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
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
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.
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.
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.
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).
Peripherals
Peripherals such as timer/counters and A/D converters will be discussed in chapters devoted to the subjects.
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.
0 x 3 F F _______________________
A limited amount of data may be stored in program memory (see Writing Programs chapter).
11
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
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
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
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.
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.
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.
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.
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.
20
Programming Software
MPLAB
PICSTART Plus
MPLAB
21
22
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
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
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.
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
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
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
34
35
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
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
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
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
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
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
44
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);
46
Relational operators
== < <= 1= Equal to Less than Less than or equal to Not equal to
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
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
#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
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
#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
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.
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
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
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);
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
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
FUNCTION
SYNTAX
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)
57
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
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
//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;
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
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
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;
-- *- 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.
ala pictl.asm
Cslb
{
outputb (ObOOOO1111) ; while (TRUE); //bit pattern to port B //circle, always
74
////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.
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
{
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
{
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
{
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
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
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.
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.
+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.
{
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;
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;
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.
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);
(0x02);
(0x03);
(0x04);
(Obi1111111);
}
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
////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
//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
Cs8a
{
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
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
//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.
+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.
Cs9a
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.
The NOT logical operator negates the logic of the expression it operates on.
! expression will evaluate to FALSE if expression is TRUE.
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.
+5VDC
96
{
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.
//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
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));}
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
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 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
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);
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.
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
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
{
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;
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.
+5VDC
////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
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.
112
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.
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
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 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
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
/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
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
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
////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
////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
'
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.
DPgfe dcba 0011 0000 0101 0100 0110 0110 0111 0000 0111 0110 1111 0110 1011 1111 0110 1101 1101 0111 1111 1111
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;
"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
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;
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
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;
//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
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).
SEND
ltS-232 CONVERTER
RECEIVE
141
{
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
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.
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
Logical operators
&& And I I Or ! Not
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
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
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).
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.
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.
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
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
} 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
+5VDC
155
Main . Program
LEDs Off
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
} 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
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
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
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
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.
*
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);
163
Main Program
Start Count
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
166
Main Program
Start Count
////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
Main Program
L to H Rising Edge
Start Count
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);
170
Main Program
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()
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
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
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
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
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 [email protected]
Digi-Key
701 Brooks Avenue South Thief River Falls, MN 56701-0677 Tel 800 344 4539 Web http://www.digikey.com
Jameco Electronics
1355 Shoreway Road Belmont CA 94002-4100 Tel 800 831 4242 Fax 800 237 6948 Web http://www.jameco.com email [email protected]
Marlin P. Jones & Assoc Inc
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 [email protected]
Microchip Technology Inc.
2355 W. Chandler Blvd. Chandler AZ 85224 Tel 480 792 7200 Web http://www.microchip.com
181
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
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