PIC Microprocessor Circuit Design
PIC Microprocessor Circuit Design
PIC Microprocessor Circuit Design
John B. Peatman
Professor of Electrical and Computer Engineering Georgia Institute of Technology
Qwik&Low Books 1418 Iroquois Path, NE Atlanta, GA 30319 (770) 457-6133 [email protected]
Trademark Information: PIC, PICmicro, nanoWatt Technology, PICkit 2, MPLAB, C18, MPASM, MPLINK are trademarks of Microchip Technology Incorporated in the United States and other countries. All other trademarks mentioned in this book are property of their respective companies. The author makes no warranty of any kind, expressed or implied, with regard to the program code contained in this book. The author shall not be liable in any event arising out of the use of this program code.
This book is available as a $15.50 print-on-demand paperback book from www.lulu.com or as a free download along with supporting material from www.qwikandlow.com
To six former students who changed the direction of my professional life: Jim Carreker and Neal Williams 19681969 Joe Bazzell and Larry Madar 19891990 Rawin Rojvanit and Chris Twigg 20012002
CONTENTS
Preface 7 Chapter 1 1.1 1.2 1.3 1.4 1.5 1.6 Chapter 2 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 Chapter 3 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 3.10 Chapter 4 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9 4.10 4.11 4.12 4.13 4.14 4.15 Introduction 13 Low-Power Designs 13 The Learning Curve 14 The PIC18LF4321 Microcontroller 15 The PIC18LF6390 LCD Controller 15 QwikBug Development Environment 17 Programming with the PICkit 2 Programmer 17 Low-Power Operation 18 Overview 18 CR2032 Coin Cell 18 A PIC18LFXXXX Family of Microcontrollers 19 INTOSC and INTRC, the Internal Oscillators 21 Intermittent Sleep Mode Operation 21 Effect of Clock Frequency 25 User Program Steps to Reduce Current Draw 26 The Qwik&Low Board 27 Coding in C 28 Qwik&Low Board 31 Overview 31 Equipment Setup 31 Input/Output Peripheral Power 33 Power Switching and Current Monitoring PICkit 2 Programmer Connection 35 Effect of Coin Cell Aging 37 Watch Crystal Circuitry 38 Qwik&Low LCD 38 Expansion Header 40 Summary of MCU Pin Use 40 A First Template Program (T1.c) 43 Overview 43 A T1.c Template Program 43 Configuration Selections 47 Global Variables 50 Bit Manipulations 50 Function Prototypes 51 A Calibrated Delay Macro 51 Main Function 52 8-bit and 16-bit Registers 53 Clock Rate Choice 53 Analog Pins Versus Digital I/O Pins 54 Digital Inputs Versus Outputs 54 Brownout Module Disabling 54 Main Loop 55 Compilation 56 Problems 57 SPI Bus and the LCD (T2.c) 58 Overview 58 Serial Peripheral Interface 58
Display Strings 61 Displayable Characters 62 Decimal Point 62 T2.c, A Display Template 63 Initialization of Two Microcontrollers 68 SPI Initialization 68 The Display Function and LCDSTRING 68 The Time Function 69 The Pushbutton Function 69 Problems 70 PC Monitor Use (Measure.c) 71 Overview 71 Waveforms and Baud-rate Accuracy 72 UARTs TX Circuitry and Use 76 UART Initialization 76 TXascii Macro 78 Number-to-ASCII Conversion 78 Measure.c, a Cycle Counting Program 80 Problems 88 Reorganization of Timing Via Interrupts (T3.c) 89 Overview 89 Low- and High-Priority Interrupts 89 Interrupts and the C18 Compiler 91 Timer1 Oscillator 91 Timer1 Counter 94 Timer3 Counter 96 The T3.c Template Program 96 Problems 96 Stepper Motor Control 107 Overview 107 Stepper-Motor Operation 107 Bipolar Versus Unipolar Stepper Motors 111 Stepper-Motor Driver 112 Stepping 115 Problems 116 Analog-To-Digital Converter 119 Overview 119 Qwik&Low Analog Versus Digital Pins 119 ADC Result Alternatives 122 Reference Voltage Choice 123 ADC Timing 123 ADC Input Selection and Conversion 125 ADC Conversion During Sleep 126 AD22103 Temperature Sensor 127 Problems 132 Rotary Pulse Generator (RPG.c) Overview 133 RPG Resolution 133 RPG Functionality 134 133
35
Contents
10.4 10.5
The RPG Function 136 The Detented RPG 138 Problems 149 Measurements 150 Overview 150 Code Size 150 Code Execution 153 Variable Code Execution 153 Interrupt Timing Measurement 155 Time Spent Doing Useful Work in Main Loop Cleaning Up Surprises 156 Problems 157 Interrupts 159 Overview 159 MCU Interrupt Response 159 Compiler Handling of Interrupts 160 Using One Priority Level Only 163 PIC18LF4321 Interrupt Sources 165 Use of the Interrupt Mechanism + Idle Mode External Interrupts 165 PORTB-Change Interrupts 167 Problems 168 Timing Measurements Revisited (Calibrate.c) Overview 169 Timer0 Operation 170 Internal Oscillator Calibration 173 External Time Measurement 181 Start, Stop, and Send Functions 183 Problems 183 EEPROM (EEtest.c) 187 Overview 187 EEPROM Use 187 EEPROM Registers and Functions 188 Multiple Write Sequences 191 Protecting the Write Sequence 191 EEPROM Life 198 Problems 199 1-Wire Interface (SSN.c) 201 Overview 201 Interface Circuitry 201 Writing Ones and Zeros 203 Message Protocol 204 Reading Ones and Zeros 211 Generalizing from the SSN.c Template 211 Multiple 1-Wire Devices on a Single Bus 213 DS2415 1-Wire Time Chip 215 Problems 217 Starburst Display (LCD.c) 219 Overview 219 Starburst Display Configuration 219
LCD Controller Circuit 221 Multiplexed LCD Voltage Waveforms 223 LCDDATAi Register Use 224 ASCII Code to LCDDATAi Representation 225 Awakening vs. Interrupt Vectoring 229 Reception of SPI Bytes into VSTRING 229 Decimal Point 230 Data Structures 230 Problems 239 SPI for Feature Enhancement 241 Overview 241 SPI Output Functionality 242 SPI Input Functionality 245 AD5601 DAC Output 246 MOSI/MISO Terminology 249 ADT7301 SPI Temperature Sensor 251 Problems 255
156
165
APPENDICES
169 Appendix A1 A1.1 A1.2 A1.3 A1.4 A1.5 A1.6 A1.7 A1.8 A1.9 Appendix A2 A2.1 A2.2 A2.3 A2.4 A2.5 A2.6 A2.7 A2.8 A2.9 A2.10 A2.11 Appendix A3 Appendix A4 Index 306 QwikBug Program Debugger 256 Introduction 256 Installation 258 Graphical User Interface 262 Loading a Program 262 Program Control 264 Breakpoints 266 Watch Variables 267 Additional Features 273 Keyboard Shortcuts 276 Interpreting .Lst Files 277 Overview 277 Harvard Architecture 277 Instruction Set and Direct Addressing 279 F/W Distinction 279 Name Replacements for Operand Addresses 283 Counting Cycles 283 Flag Bits 285 Indirect Addressing of RAM Variables 286 CPU Registers 287 Indirect Addressing of Program Memory 289 Special Function Registers 289 Qwik&Low Board in Detail 293 300
PREFACE
The premise of this book rests on the reality that as embedded microcontrollers reach into virtually all corners of modern life, many of these applications can take advantage of the benefits accruing when powered from a coin cell battery. Some of these are: small product size, reduced product cost, enhanced design simplicity, portability, electrical isolation.
Manufacturers have met this reality with low-power, extended-supply-voltage versions of their microcontroller chips. For example, Microchip Technology, the number one supplier of 8-bit microcontrollers in the world, is using what they call nanoWatt Technology features to upgrade their entire microcontroller product line by: enhancing the feature set of an older part with new power-controlling modules and new power-sensitive operating modes, reducing leakage current, extending the supply voltage range from 5.5 V down to 2.0 V, while, at the same time, reducing chip cost. The new parts will handle the old applications. In addition, they open the door to a wide range of new applications. The intent of this book is to explore how these features impact the design process and the new opportunities these features make possible. This book employs the Qwik&Low board, shown on the cover of this book, as the vehicle for the reader to carry out these explorations. The board uses two Microchip PIC microcontrollers mounted on the under side of the board, one as a general purpose microcontroller (PIC18LF4321) and one as the controller (PIC18LF6390) for the boards liquid crystal display. The board was designed by the author and is available as Microchip Technology's Part No. DM183034. For helpful purchase information, see http://www.qwikandlow.com/purchase/. Another goal of this book has been to provide readers with a low-cost tool for their learning. It introduces the reader to code writing for a microcontroller using the C programming language. Microchips C18 compiler, available to anyone in the form of a free student version, is used throughout. In fact, with just the few constraints introduced, and used, throughout the book to minimize the execution time of algorithms, the student version of the compiler produces machine code that is virtually identical to that produced by the commercial version.
7
Preface
Free supporting tools are available at the authors website, www.qwikandlow.com. The centerpiece of this support is QwikBug, a debugging user interface. QwikBug supports: downloading of an application program to the Qwik&Low board, running the program, stopping at a breakpoint, stepping from one line to the next of the C source file, monitoring or changing the content of selected variables and registers.
QwikBug employs the same Background Debug Mode used by Microchips debugging tools but does so using nothing more than a serial connection (via either a serial cable or a USB-toserial adapter) to a PC. This is the authors seventh textbook, with four earlier books published by McGraw-Hill and two by Prentice Hall. In gratitude to the many students who have supported the authors activities over many years, the book is being made available at no cost. However, this decision has its downside. By foregoing the fine and generous support of a commercial publisher, the author will miss their astute help in having it reach its intended audience of professors, students, and professionals. The free download of the book is available from the print-ondemand book printer, www.lulu.com, by searching their website for Peatman. The site also lists a printed version of the book carrying the intended first printing price of $15.50, the same price as the authors first textbook, published by McGraw-Hill thirty-five years ago. While the reader awaits the one- to two-week delivery of the printed edition, the free downloaded version can be used.
Preface
An appendix is included for those who are interested in studying how a C algorithm is implemented in the assembly language of the PIC18LF4321 microcontroller. This is especially useful when code is being debugged that does not seem to do what it is intended to do. The Microchip compiler is wrapped in a c18.exe utility that not only compiles the C code into machine code but also generates a qwik.lst file. The appendix explains how this file can be used to understand program code execution. The book begins with the perspective of Steve Sanghi, CEO and President of Microchip Technology, on low-power designs. The first three chapters present an overview of the environment of the book, low-power operation, and the Qwik&Low board used as a vehicle for all that follows. Chapter 4 introduces the first template program and lays the groundwork for understanding and using it. Chapter 5 extends the first template to the use of the Qwik&Low boards liquid-crystal display. Chapter 6 opens the door for an application program to be tested, with measurement results displayed on the PC monitor. Chapter 7 introduces the use of interrupts to control the timing of events with counters controlled by a crystal oscillator. Chapter 8 presents an interesting digression. In this chapter a stepper motor and its controller circuit draw power from their own power supply and take control inputs from the Qwik&Low board, serving as a model for more general expansion. For a course that is driven by a sequence of lab projects, Chapters 9 and 10 are likely to be studied and used, at least in rudimentary form, earlier than the consecutive sequence of chapters would dictate. Chapter 9 explains the use of the microcontrollers analog-to-digital converter. Early in the course, a project might use it to introduce a variable into an algorithm with the ADC output from a one-turn potentiometer input. The chapter also deals with the scaling and display of a ratiometric temperature sensors output. Chapter 10 discusses the use of a rotary pulse generator (a.k.a., a rotary encoder) to vary an input parameter to an application. Chapters 11 and 13 provide two takes on timing measurements. Chapter 11 deals with the monitoring of the execution time of an algorithm, a critical issue for average current draw and an elusive issue when coding is done in C. Interrupt timing measurements are also studied. Chapter 13 looks at how the microcontrollers 2% accurate internal oscillator can be calibrated against the 32768 Hz, 50-ppm (parts per million) watch crystal oscillator used by two of the microcontrollers timers. It then goes on to convert accurate cycle count measurements into accurate microsecond measurements. Chapter 12 explores in depth the chips full interrupt capabilities that were first introduced in Chapter 7. It discusses the drastic effect that alternative ways of expressing interrupt service routines in C can have on execution time. Chapter 14, like Chapters 9 and 10 before it, might well be introduced before its sequential order in the book. It describes how the nonvolatile EEPROM in the chip can be used to save and restore data through power disruptions. Chapter 15 explains the 1-wire interface protocol used by Dallas/Maxim for a family of parts. These parts include the silicon serial number chip that gives each Qwik&Low board a unique serial number. Chapter 16 describes the operation of the PIC18LF6390 LCD controller chip and its firmware. This illustrates the operation of a low-power chip dedicated to a specific, limited task. It carries out the task while drawing an average current of only 6 A, orders of magnitude less than the current drawn by popular dot-matrix, multiple-character, 5 V displays.
10
Preface
Chapter 17 considers another role for the serial peripheral interface used by the microcontroller to send data to the LCD controller. This interface can be used for attaching chips with additional I/O features to the microcontroller. Two chips are considered: a digital-to-analog converter, and a temperature sensor with a high-resolution digital output. The book concludes with four appendices. The first describes the installation and use of the QwikBug debugger. The second describes the CPU structure, instruction set, and addressing modes of the PIC18 microcontroller family. The intent is to gain an understanding of the assembly code produced by the compiler of an application program written in C. The last two appendices describe the circuitry of the Qwik&Low board and of the stepper motor control board.
ACKNOWLEDGMENTS
It has been my good fortune over the last several years to be guided in my academic endeavors by Gary May, Chair of the School of Electrical and Computer Engineering at the Georgia Institute of Technology, and by Roger Webb, Garys predecessor. This book would not have been written but for the assistance of some able students. Every line of C code in the book was either written by Alex Singh or written by the author and vetted by Alex. He suggested numerous alternatives for expressing a given algorithm in C, all in the interest of examining alternatives produced by the C compiler. He wrote the code for the EEPROM of Chapter 14, for the silicon serial number chip of Chapter 15 and the elegant display controller code of Chapter 16. Alex developed the c18.exe compiler wrapper that invokes the Microchip C18 compiler with carefully selected parameters chosen to optimize code efficiency and execution time and to produce the same resulting machine code for both the commercial version of the compiler and the student version. Alexs wrapper displays the number of bytes of machine code produced by the application program as well as the percentage of available program memory used by the program. Alex also developed the first pass of the www.qwikandlow.com website, posting everything needed by the authors students as they used the book in manuscript form during the 2007 fall semester. Ryan Hutchinson developed the QwikBug user interface and Kenneth Kinion the QwikBug executive for use with assembly language source files during the 2007 spring semester. Ryan graduated from Georgia Tech in May 2007 and generously agreed to spend nights and weekends during the summer reworking the user interface for source files written in C. The result is a rock-solid tool validated by a semester of student use and fully described by Ryan in Appendix A1. Louis Howe developed QwikProgram 2, a utility needed to program Kenneth Kinions QwikBug executive into the PIC18LF4321 chip. This is a task that is thwarted by Microchips programming utilities. QwikBug requires a programmed Background Debug Mode vector because Microchips utilities reserve the BDM vector use for their own debugger. A purchased Qwik&Low board includes this QwikBug executive programmed into its PIC18LF4321 chip. David Bauer developed QwikIndent, a utility that reformats the indenting of a source file and aligns the comments appended to the lines of C code. He also developed QwikPH, a utility to update the Program Hierarchy table showing the relationships between all the functions in an application program. Both of these utilities employ the comforting feature of
Preface
11
renaming the source file before producing a new modified source file and thus providing the ability to revert to the original source file, if desired. Cody Planteen developed the QwikLst utility. As described in Appendix A2, it is included in the invocation of the c18.exe utility to enhance the resulting assembly language list file into a more readable qwik.lst file. It thereby helps to clarify the C compilers implementation of an application program. I have long been indebted to Rick Farmer for his help with the physical design and PC board implementation of assorted projects over the years. Ricks extensive design experience once again has proven crucial to the development of the Qwik&Low board and also of the stepper motor controller board. At Microchip Technology, application engineers (and former students) Rawin Rojvanit and David Flowers have been quick with answers to elusive questions or guides to their appropriate colleagues. Thus Brett Duane brought clarity to a start-up problem with the Timer1 oscillator. Naga Maddipati helped to track down and fix an elusive leakage current problem in the PIC18LF6390 LCD controller. Steve Sanghi and Eric Sells provided valuable material and insights for Chapter 1. Bill Kaduck and Dave Cornish of MICRODESIGNS, Inc. have shared design ideas that have enhanced the design of the Qwik&Low board. Mark Schutte, my son-in-law and master photographer, has produced all of the photographs in this book. He also designed the books cover. Leland Strange, President and CEO of Intelligent Systems Corporation, has long provided professional insights as well as fostering my continually evolving class and laboratory activities at Georgia Tech. This low cost but quality book would not have happened without the gracious and accommodating help of Saradha, Project Manager, and Erin Connaughton, Full Service Manager, with Laserwords of Chennai, India. They have managed the rendering of my penciled line drawings into finished artwork and have produced the finished book pages. Finally, with the publication of my seventh textbook, I am once again indebted to my wife, Marilyn. From the outset she has supported the development of a text that could serve the reader with the gift of a free downloadable text. Marilyn has translated my handwritten words into readable, understandable text and has been involved with every decision in the production of the book. She brightens the days of my life. John B. Peatman Georgia Institute of Technology [email protected]
Chapter
INTRODUCTION
1.1 LOW-POWER DESIGNS: THE WAY FORWARD IN EMBEDDED APPLICATIONS1
The need to reduce overall power consumption plays a crucial role in many embedded applications. This intention could be twofoldto extend battery life or meet regulations like Energy Star. Low power and dependable operation are important in embedded applications. As microcontroller designs continue to move into smaller applications with limited power resources, the availability of economical small-pin-count devices with complex peripherals and power-saving features has become increasingly desirable. Batteries are the power source in low-power designs. Since the advances in battery technology are incremental in nature, it is left to the ingenuity of the embedded designer to get the most out of the power source using suitable microcontrollers and related devices. This can be seen in examples such as PDAs, mobile phones, media players, laptops and other devices. System designers face many challenges posed by compact and portable device electronics. Chip designers have incorporated several power-saving features into their devices that give designers control over power consumption. The main focus of a successful
1 This section was written by Steve Sanghi, CEO and President, Microchip Technology Inc., Chandler, Arizona.
13
14
Chapter 1
Introduction
low-power design is a microcontroller that features a variety of sleep modes and clock modes. The idle modes of the microcontroller power down the CPU while allowing peripherals such as an ADC to continue to operate. To conserve power, in most applications, system controllers need to remain in a low-power state most of the time, waking up periodically under a timers interrupt to run program code. Several techniques exist that allow designers to save power. The most obvious one is being able to turn off the peripherals when they are not needed. For example, the Brown-out Reset (BOR) feature is not necessary in battery-powered applications. On the other hand, designers can turn off the CPU using an idle instruction and keep the peripherals running. By invoking the sleep state, the power consumption can be reduced by as much as 96%. Power saving can be optimally achieved in low-power designs by having the microcontroller control power used by both internal and external peripherals. This requires the partitioning of the design based on power consumption during its operation. When designing a low-power product, determine the required operation states and plan to shutdown unwanted circuitry. As a rule of thumb, if a single peripheral in the device consumes most of the power, worrying about reducing the microcontrollers power will have no impact on the overall system power consumption. Safety is a high priority in some applications such as medical and mission-critical applications. In these applications, system designers need to provide for emergency situations where an appliance can suffer from loss of power or program control. There could be instances where the loss of a clock source can trigger an erroneous execution of a products control program. In certain microcontrollers, designers can take advantage of a fail-safe clock monitor feature to detect the loss of a clock sourcethus helping the system toward either a gentle shutdown or a stay-alive mode, if shutdown is not desired. By using the latest microcontrollers, designers can implement power-management techniques and build cost-effective low-power devices. Minimizing power consumption in embedded systems enables the use of smaller batteries in portable systems. The combination of lower-power peripherals and microcontroller sleep modes improves the design of a low-power solution. This opens up new product design opportunities in space-constrained applications that could not afford the cost of a microcontroller. The impact of low-power designs can have significant implications from disposable medical devices to consumer electronics and beyond.
Section 1.4
15
multipurpose microcontroller. The other concentrates its feature set around the ability to serve as a versatile LCD controller that supports a variety of LCD multiplexing modes and a variety of serial and parallel data input formats. Microchips 8-bit microcontrollers span from 6-pin to 100-pin parts, each with its own set of on-chip peripheral modules (e.g., an analog-to-digital converter). Microchip has pioneered the use of low-cost reprogrammable flash memory, accounting for over 2 billion of the 5 billion microcontrollers it has sold to date. For years Motorola dominated the 8-bit microcontroller market with one out of every three microcontrollers sold being a Motorola product. In 2002, a marketplace increasingly supplied by many competitors had reduced Motorolas market share and led to Microchip gaining the number one spot in the number of 8-bit microcontrollers shipped. Microchip has continued to hold this number one spot since then and has, this year, also gained the number one spot in 8-bit microcontroller revenue. Given this fractionated market and given the benefits that follow market share, Microchip has pursued a careful learning curve strategy. The principle of the learning curve states that each doubling of the quantity of parts produced results in a fixed percentage decrease in the unit cost of a part. By passing these reduced costs along to customers in the form of reduced prices, Microchip has gained market share. As a consequence, even sophisticated parts such as those used on the Qwik&Low board have a unit price of about $3. The payoff for users of this book is that their learning is directed toward a family of parts that finds wide use and is competitively priced. Just as important, readers will learn techniques for using a microcontroller in an energy-efficient manner that will translate to another manufacturers microcontroller. Even as students graduate and find themselves immersed in a company environment with tools and facilities organized to use another manufacturers microcontroller, the skills and perspectives gained here will reap dividends there.
16
Chapter 1
Introduction
PIC18LF4321 CPU clock INTOSC INTRC 8 kHz to 2 MHz 8 kHz, low power Interrupt logic Timers Timer1 Timer3 Timer0 CPU clock CPU Run-idle-sleep modes 2.0 V to 5.5 V operating range High priority Interrupts Low priority Data RAM Data EEPROM 512 bytes CCP2 256 bytes Capture/Compare/PWM modules Flash program memory 8192 bytes (4096 16-bit instructions) Configuration bytes to enable/disable features Power-on reset & start-up timers Enable/disable Enable/disable & threshold Enable/disable & timeout period Serial Peripheral Interface SCK SDO SDI TX RX Timer2 (configure with timers) CCP1 CCP1 Timer1 oscillator 32768 Hz crystal
Other options
CCP2
Brown-out reset Watchdog timer wakeup or reset 4 ms to 131 sec Program code protect
Ten-bit analog-to-digital converter (up to 13 channels) Two voltage comparators High-low supply voltage detect (16 levels)
FIGURE 1-1 Block diagram of PIC18LF4321 microcontroller when a string of characters making up a new message is received by the chip does the CPU awaken and process the characters, load the registers used autonomously by the LCD module, and then return to sleep.
Section 1.6
17
Chapter
LOW-POWER OPERATION
2.1 OVERVIEW
This chapter considers the opportunities and challenges of using a popular MCU (microcontroller unit) under the constrained design goal of powering it with a lowcost 3-V coin cell. The specifications of the coin cell will lead to a consideration of the microcontroller and its power-saving operating modes. A vehicle for testing operating alternatives will take the form of a manufactured Qwik&Low board. A case will be presented for writing code in C rather than in assembly language.
18
Section 2.3
19
(a) Coin cell and a dime Nominal voltage = 3 V Rated capacity = 220 mAh (milliampere-hours) Standard discharge current = 0.4 mA (b) Specifications
3.0
Voltage (V)
2.0
1.0
20
Chapter 2
Low-Power Operation
(a) Price versus program memory and RAM size (prices from www.microchipdirect.com).
Eight internally generated, 2% accurate CPU clock frequencies (2 MHz down to 8 kHz). Separate very low power but 10% accurate internal 8 kHz CPU clock. Another very low power oscillator using external 32768 Hz watch crystal for 50 ppm accuracy. Power-managed modes of operation: Run mode: CPU on; peripherals on Idle mode: CPU off; peripherals on Sleep mode: CPU off; peripherals off Operation down to 2.4V for CPU clock of 2 MHz or less. Watchdog timer for periodic wakeup from lowest-power sleep mode. Timeout period of 4 ms, 8 ms, 16 ms, 32 ms, . . . , up to over 2 minutes. (b) Low-power-achieving features.
Up to 32/25 I/O pins on 44-pin/28-pin parts. Four sixteen-bit timers plus associated circuitry for versatile timing measurement and control. 13/10 analog-to-ten-bit-digital converter inputs (44-pin/28-pin parts). Two voltage comparators. SPI and I2C serial peripheral expansion support. USART asynchronous and synchronous serial communication support. Twenty interrupt sources. (c) Input/output functions FIGURE 2-2 Family features of four PIC nanoWatt Technology microcontrollers The table of Figure 2-2a lists the distinguishing amounts of program memory and RAM data memory plus the 2007 price for four otherwise virtually identical pairs of chips. Each pair includes a 44-pin surface-mount part and the same microcontroller in
Section 2.5
21
a 28-pin DIP package (with 11 of its I/O pads not brought out to DIP pins). Although even the 4221/2221 pair with 4,096 bytes of program memory should suffice for the tasks of this text, the sweet spot 4321/2321 pair, with double the program memory for essentially the same price, will be used throughout.
22
Chapter 2
Low-Power Operation
FCPU
OSCCON = x 0 0 0 x x x x AND OSCTUNE = 0 x x x x x x x (a) CPU clock derivation from INTOSC or INTRC oscillators.
OSCCON 0 0 1 0 System clock select bits 0 0 0 1 1 0 Use primary oscillator selected by configuration choice (Figure 4-2) Use Timer1 (32768 Hz crystal oscillator) Use internal oscillator block (INTOSC or INTRC) FCPU (kHz) (FOSC/4)
FOSC (kHz) 1 1 1 1 0 0 0 0 1 1 0 0 1 1 0 0 1 0 1 0 1 0 1 0
8000 2000 4000 1000 2000 500 1000 250 500 125 250 62.5 31.25 125 For this choice, use OSCTUNE to select 7.8 kHz source
OSCTUNE
1 0
use INTOSC; FCPU = 8000/1024 = 7.8 kHz 2% use INTRC; FCPU = 31.25/4 = 7.8 kHz 10% (low-power alternative) (b) Clock selection registers
The watchdog timer can be enabled with a bit of a configuration byte, once and for all, independent of what a user program might choose to do. Alternatively, as shown in Figure 2-6, setting or clearing the SWDTEN bit of the WDTCON register can leave the enabling/disabling under program control. Whenever the watchdog timer is
Section 2.5
23
Clock source INTOSC INTOSC INTOSC INTOSC INTOSC INTOSC INTOSC INTOSC INTRC
Nominal Fosc 8 MHz 4 MHz 2 MHz 1 MHz 500 kHz 250 kHz 125 kHz 31.25 kHz 31.25 kHz
Actual Fcpu (Fosc/4) 1.966 MHz 983 kHz 492 kHz 246 kHz 123.5 kHz 61.7 kHz 30.8 kHz 7.69 kHz 8.19 kHz
CPU clock period 0.508 s 1.02 s 2.03 s 4.06 s 8.10 s 16.2 s 32.5 s 130 s 122 s
Current draw 1.750 mA 1.036 mA 674 A 486 A 390 A 343 A 207 A 206 A 64 A
FIGURE 2-4 Current draw of PIC18LF4321 when operating continuously from a 3V coin cell versus CPU clock source and frequency. (All internal and external functions other than CPU instruction execution are inactive.)
enabled, the low-power INTRC oscillator is enabled for use with the watchdog timer, independent of whether the INTRC oscillator is also selected as the CPU clock. The running of INTRC plus the watchdog timer together draw a measured current of 2.2 A, a miniscule current for implementing this intermittent sleep mode of operation. The choice of the watchdog timers divider can be used to shorten response time to input/output demands. Alternatively, it can be used to reduce average current by lengthening the Tperiod interval of Figure 2-5. Another issue bearing on this choice is the effect of the contact bounce exhibited by pushbutton switches and keypad switches employed in a user interface. If Tperiod is selected to be larger than the maximum contact bounce time of any switches in an application, then sensing the state of a switch during successive intervals effectively debounces the switch, as shown in Figure 2-7. Figure 2-7a illustrates a circuit for sensing the press of a pushbutton. During successive awakenings of the chip, the input is
1750 A
FIGURE 2-5 Effect of intermittent sleep mode upon average current. (shown for FOSC = 8 MHz, FCPU = 2 MHz)
24
Chapter 2
Low-Power Operation
WDTCON SWDTEN = 0: Disable watchdog timer 1: Enable both INTRC oscillator and watchdog timer
N is set by the WDTPS configuration value 128 Period = 4 ms (a) Circuit WDT OFF : ON : N scaler Tperiod Wake up sleeping CPU
Watchdog timer disabled; control is passed to the SWDTEN bit of the WDTCON register Watchdog timer is enabled, regardless of the state of the SWDTEN bit Tperiod 4 ms 8 ms 16 ms 32 ms 64 ms
WDTPS 1 2 4 8 16
32768
2 minutes
(b) Configuration options programmed into the chip (unavailable for change by the user program) I(INTRC+WDT) = 2.2 A (c) Measured value of current
FIGURE 2-6 Watchdog Timer read. Action is taken when the pin is read as a 0 and the previous reading was a 1. Before the keypress, a succession of 1s was read. While the key is held down, a succession of 0s is read. If the key is bouncing at the moment the key state is read and if it is read as a 1, then a single change from 1 to 0 will be detected at the time of the following
Section 2.6
25
0 or 1
sample. If the bouncing key is read as a 0, a single change from 1 to 0 will be noted at the time of this sample. In neither case does the 1110101000 sequence register as anything other than a single 1 0 transition. The maximum contact bounce time of small electromechanical switches is commonly specified as less than 10 ms. If Tperiod = 4 ms, the user program just needs to check the state of a switch every third awake time to debounce it. For Tperiod = 8 ms, the switch can be checked every other awake time. For Tperiod = 16 ms, the switch is checked every awake time, with no keybounce ever seen.
26
Chapter 2
Low-Power Operation
The conclusion to be drawn from Figure 2-8 is dramatic. The choice of a high value of FOSC minimizes current draw. At the other extreme, the choice of the lowcurrent INTRC oscillator produces a higher average current draw than all but the lowest FOSC derived from the INTOSC oscillator. It is important to remember that these conclusions rest on having an application that is not delayed by waiting for the completion of a task by a peripheral module.
Clock source INTOSC INTOSC INTOSC INTOSC INTOSC INTOSC INTOSC INTOSC INTRC
Nominal Fosc 8 MHz 4 MHz 2 MHz 1 MHz 500 kHz 250 kHz 125 kHz 31.25 kHz 31.25 kHz
Pcpu Actual CPU clock period 0.508 s 1.02 s 2.03 s 4.06 s 8.10 s 16.2 s 32.5 s 130 s 122 s
Texec Time to execute 100 clock periods) 50.8 s 102 s 203 s 406 s 810 s 1,62 ms 3.25 ms 13.0 ms 12.2 ms
Section 2.8
27
Clock source INTOSC INTOSC INTOSC INTOSC INTOSC INTOSC INTOSC INTOSC INTRC
Nominal Fosc 8 MHz 4 MHz 2 MHz 1 MHz 500 kHz 250 kHz 125 kHz 31.25 kHz 31.25 kHz
Irun Run mode current (CPU on; peripheral on) 1750 A 1036 A 674 A 486 A 390 A 343 A 207 A 206 A 64 A
Iidle Idle mode current (CPU off: peripherals on) 834 A 503 A 325 A 235 A 179 A 158 A 148 A 140 A 5 A
Iidle Irun .48 .49 .48 .48 .46 .46 .71 .68 .08
FIGURE 2-9 Idle mode current compared with run mode current discussed can be used. These microcontrollers include an idle mode. When the IDLEN bit in the OSCCON register is set, the subsequent execution of a sleep instruction will stop the clock to the CPU, but maintain it to the peripheral modules. Virtually all of the internal peripheral modules include an interrupt mechanism that can be set up to awaken the CPU when its task is complete. As shown in Figure 2-9, stopping the clock to the CPU while maintaining it to the chips peripheral modules cuts the current draw by one half for any but the lowest frequencies of the INTOSC oscillator. The 92% reduction in current when clocking only the chips peripheral modules with the INTRC oscillator may be a reflection of how little current the low-power oscillator itself draws. In contrast, the leveling off of both the run-mode current and the idlemode current of the chip when clocked by the INTOSC oscillator and its scaler for its lower frequencies may be a reflection of the substantial current drawn by the 8-MHz oscillator itself.
28
Chapter 2
Low-Power Operation
FIGURE 2-10 Qwik&Low board The PIC18LF4321 can be programmed with Microchips low-cost ($35) PICkit 2 programmer shown probing the Qwik&Low board in Figure 2-11. Alternatively, the PIC18LF4321 can be linked to a PC via either a $5 serial cable (for PCs having a DB-9 serial port) or a $10 USB-to-serial adapter. Then a free QwikBug utility (discussed in Chapter Three) running on the PC can download and run a C-compiled hex file on the PIC18LF4321. QwikBug supports debugging with breakpoint capability, single stepping, and variable watching and modifying. A window in QwikBugs display can be written to by a user program to extend the display capability of the board beyond that of the eight-character LCD.
2.9 CODING IN C
Most microcontroller applications developed professionally have had their code written in C rather than in the microcontrollers assembly language. There are several reasons for C to be preferred: C code, even without comments, is close to being self-documenting. Thus it is easier for others to understand and augment the original developers code. The C compiler, rather than the code developer, handles functions such as multiplication, division, and table lookup that are built into standard C.
Section 2.9
Coding In C
29
FIGURE 2-11 PICkit 2 programmer The need to understand the role of the microcontrollers CPU structure (i.e., its registers, addressing modes, and instruction set) is passed to the C compiler and, thereby, bypassed by the code developer. While there is some justification for lamenting the resulting loss of control over how algorithms are carried out by the microcontroller, there is the compensating accuracy of the resulting C implementation. Writing code in C is not without its downsides: The resulting machine code will be larger than if it had been written in the microcontrollers assembly language. This is generally not a problem, as long as the microcontroller has sufficient program memory to hold the machine code. The code developed in C may not execute as fast as if it had been developed in assembly language. However, for issues that really matter, such as measuring a pulse width precisely, the microcontroller includes resources to take over this role, independent of the speed of execution of the program code. C compliers are generally expensive. However, whereas the commercial version of Microchips C18 compiler is pricey, their student version is available free to anybody and provides exactly the same features and optimized compilation for 60 days. After that, only the optimization is reduced. Even so, the resulting machine code is generally satisfactory. The code writer must be, or become, familiar with writing code in C. Although this is becoming a common skill for electrical and computer engineering students,
30
Chapter 2
Low-Power Operation
it is certainly not universally so. However, it is the authors experience that template programs can be used for this purpose. These template programs can progress through a sequence of increasingly complex tasks. Along the way, student projects can build on a given template by doing more of the same with an added peripheral device. Ultimately, the role of the developer of a microcontroller application is to understand the functioning of peripheral devices external to the chip (e.g., a temperature sensor) as well as peripheral modules within the chip. Program control of these peripherals will reduce to testing status bits in registers, setting or clearing control bits in registers, and reading from and writing to registers. These steps are virtually the same, whether implemented in C code or assembly language code. The understanding of how to deal with peripheral devices will be a central theme of this book.
Chapter
QWIK&LOW BOARD
3.1 OVERVIEW
The chapter begins with a brief list of items needed to support the book using the Qwik&Low board. The I/O and support circuitry surrounding the PIC18LF4321 on the board are described, as is the boards LCD circuitry.
32
Chapter 3
Qwik&Low Board
FIGURE 3-1 Required Qwik&Low supplies authors website, www.qwikandlow.com, to obtain and install the QwikBug utility to run on a PC. This free utility, prepared by Ryan Hutchinson and Kenneth Kinion not only allows the user to download and run a compiled C file on the PIC18LF4321, but also to stop at a breakpoint and single step line by line through the C source file while monitoring and optionally modifying selected watch variables. If the user chooses to overwrite the QwikBug utility in the chip, Microchips PICkit 2 programmer ($35) will be needed. If the user later decides to reinstall QwikBug into the boards MCU (microcontroller unit, the PIC18LF4321), a QwikProgram 2 utility developed by Louis Howe is available to download from the www.qwikandlow.com website and then install and run on a PC. QwikProgram 2 programs not only the QwikBug utility itself but also the normally inaccessible Background Debug Mode vector located at address 0x200028. QwikBug employs a serial connection to a PC. To meet the requirements of the RS232 standard, such a connection requires the transmit and receive lines of the MCU to be inverted and voltage-translated so that the MCUs 0 V and 3 V levels communicate appropriately with the PCs +15 V and 15 V levels. However, for this nonstandard test serial port, the Qwik&Low board employs transmit and receive signal inversion built into the MCU itself. The MCUs receive input is clamped to the 0 V and 3 V levels by protection diodes built into the chip, with current limited by a 1 M series resistor. The MCUs transmit output voltage swing of 0 V to 3 V does not meet the RS-232 standard, but is sufficient to be interpreted satisfactorily by every PC and every serial-toUSB adapter the author and his students have tried. This simplified connection causes minimal current draw on the coin cell with and without the serial cable connected. If the readers PC includes a serial port with its 9-pin male DB-9 connector, all that is needed is the normal straight-through DB-9M to DB-9F serial cable. Straight through means that the pins of the DB-9M connector are connected to the corresponding pins of
Section 3.3
33
the DB-9F connector. If the reader has an up-to-date notebook computer, it probably does not include a serial port. In this case, the USB-to-serial adapter will be needed at a cost of $10$15, available by Googling USB to serial adapter to find any one of many sources. One last, but more expensive, piece of test equipment that finds repeated utility with the Qwik&Low board is an oscilloscope. Both the PIC18LF4321 MCU and the PIC18LF6390 LCD controller have their internal CPU (central processing unit) clock (FOSC/4) brought out to a test point on the board. By probing this point for the MCU, a user can see when the chip is awake and when it is asleep, and thereby discern the MCUs duty cycle and the effect that a low duty cycle has on current draw. The LCD controller only awakens when a new display string is sent to it by the MCU. The scope can monitor the duration of the serial transfer. It can also monitor the LCD controllers CPU clock (FOSC/4), to discover how long the LCD controller takes to process a received display string from the MCU before returning to sleep.
34
Chapter 3
Qwik&Low Board
PIC18LF4321
1 M
integral pushbutton
Jumper RD4
1 k
switched power RD3 TP5 RD2 SSN ADC RD6 (RA3) VREF+ (RA1) AN1 switched power RA7 (RA0) AN0 20 k One-turn potentiometer TP4 Ratiometric temperature sensor TP3 switched power Silicon serial number with one-wire interface 4.99 k
SPI SDO SCK (RC5) (RC3) RD5 VDD TP8 TP9 TP10
Section 3.5
35
36
Chapter 3
Qwik&Low Board
Header for MCU PICkit 2 PGC PGD GND VDD VPP DMM with 1 2000 A scale + 51.1 k VDD Test leads with banana plugs I Power switch GND PGD/RB7 PGC (RB6)
Switch to QwikBug signal Debug port (cable to PC serial port or USB to serial adapter) 5 1 M 3 2 DB-9F serial connector 1 k 100 k TP1 (RX and TX are internally inverted) TP2 (RC7) (RC6) UART RX TX
(RC1) T1OSI
15 pF
15 pF
Section 3.6
37
reads in a user program downloaded from a PC and writes it into the low addresses of the MCUs program memory. QwikBug takes advantage of the same debug mode employed by the PICkit 2 programmer. It uses the PGC (RB6) pin of Figure 3-3 to let the PCs QwikBug utility get the attention of the MCU when it is running a user program. By sending a serial character to the Qwik&Low board, the resulting wiggling of the MCUs RX UART pin also wiggles the PGC pin, as shown in Figure 3-3. This will awaken the MCU if it is asleep. Whether or not it is asleep, the user program will be interrupted and will vector to the QwikBug executive program in the MCU. The user program will be paused and control will return to QwikBug.
Normal life
1 458 days Days 120 240 360 480 Nominal capacity = 220 mAh 220 mAh Life with 20 A load = = 458 days 20 A
FIGURE 3-4 CR2032 discharge characteristic at room temperature with a 20 A load current
38
Chapter 3
Qwik&Low Board
the Timer1 oscillator will reliably start up and run in less than 0.2 s after it is enabled and will draw, together with the Timer1 counter, about 6.5 A. In contrast, with the configuration selection
LPT1OSC = ON
the Timer1 oscillator may not start up and run at all. For those boards that do start up, the startup time can be measured in seconds. For such, the current draw, together with the Timer1 counter, drops to about 1.5 A.
Section 3.8
Qwik&Low LCD
39
LCD
32 SEG31 COM3 COM2 COM1 COM0 Four backplane drivers Serial Peripheral Interface SCK SDI
32 frontplane drivers
LCDbias2
LCDbias1 VDD 475 k GND 0.1 F GND VDD RB5 INT0 SPI SCK (RC3) TP11 (RC3) (RC5) SDO PIC18LF4321 TP13 FOSC/4 Test 4PDT points switch TP10 (RC4) VDD TP12
SEG0
40
Chapter 3
Qwik&Low Board
5 2 - pin shrouded male header to mate with 10-conductor ribbon cable female header VDD RA2 Key RA5 RB1 RD0 RB0 RB3 RD1 RA4
The LCD PICkit 2 connection is required during the manufacture of the board and probably not thereafter. If there is a feature to be added to the PIC18LF6390, the LCD source code developed by Alex Singh is available at www.qwikandlow.com.
Section 3.10
41
H4 Proto Area x x x x x x x x x x x x x x x x
Pin Function
Qwik&Low Function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
RC7/RX RD4 RD5 RD6 RD7 GND VDD RB0/INT0 RB1/INT1 RB2/INT2 RB3/CCP2 ICPGC ICPGD RB4 RB5 RB6/PGC RB7/PGD MCLR/VPP RA0/AN0 RA1/AN1 RA2/AN2 RA3/AN3/VREF+ RA4 RA5 RE0 RE1 RE2 VDD GND RA7 Fosc/4 T1OSO ICRST ICPORTS T1OSI RC2/CCP1 RC3/SCK RD0 RD1 RD2 RD3 RC4/SDI RC5/SDO RC6/TX
RX input from PC or from RX' proto pin Red LED Chip select for starburst LCD Power for temperature sensor Pushbutton input
TP2 TP10
x x
(Reserved by background debug mode) (Reserved by background debug mode) Reset pushbutton AN0 input from potentiometer Input from temperature sensor Connected to RD6 for VREF+ input
x x x TP4 x x x TP3
Power for pushbutton and RPG direction RPG direction input Power for RPG interrupt input
Power for potentiometer Output of CPU clock 32768 Hz watch crystal for Timer1 osc. (Unused debugging feature) Tied to VDD to avoid 28-pin emulation 32768 Hz watch crystal for Timer1 osc. SPI clock for LCD Optional stepper DIR (pin 9 of H6) Optional stepper STEP (pin 10 of H6) I/O for DS2401 Power for DS2401 silicon serial number SPI input SPI output to LCD TX output to PC
TP6
TP9
TP5
TP8 TP1
H6 Connector x x x x x x
H3 PICkit 2
Pin Number
Test Point
42
Chapter 3
Qwik&Low Board
If a new peripheral chip is added to the board in the proto area, Figure 3-7 will help to select MCU pins that will not conflict with an already dedicated external connection. MCU pins that are brought out to the H4 header pattern simplify the connections between the MCU and the peripheral chip.
Chapter
and
*/
Alternatively, a double slash, //, tells the C compiler to ignore the remainder of a line. The first 10 lines of T1.c indicate what the program does. The Program Hierarchy consisting of the next few lines lists the functions making up the program, with indenting used to indicate that three functions are called from the main program. The function
43
44
Chapter 4
/******* T1.c ****************** * * Use Fosc = 4 MHz for Fcpu = Fosc/4 = 1 MHz. * Sleep for 16 ms (nominal), using watchdog timeout for wakeup. * Toggle RC2 output every 16 milliseconds for measuring looptime with scope. * Blink LED on RD4 for 16 ms every four seconds. * Check pushbutton and turn on LED continuously while it is pressed. * * Current draw = 4 uA (with LED and LCD switched off) * ******* Program hierarchy ***** * * main * Initial * BlinkAlive * Pushbutton * ******************************* */ #include <p18f4321.h> /******************************* * Configuration selections ******************************* */ #pragma #pragma #pragma #pragma #pragma #pragma #pragma #pragma #pragma #pragma #pragma config config config config config config config config config config config OSC = INTIO1 PWRT = ON LVP = OFF WDT = OFF WDTPS = 4 MCLRE = ON PBADEN = DIG CCP2MX = RB3 BOR = SOFT BORV = 3 LPT1OSC = OFF // // // // // // // // // // // Use internal osc, RA6=Fosc/4, RA7=I/O Enable power-up delay Disable low-voltage programming Disable watchdog timer initially 16 millisecond WDT timeout period, nominal Enable master clear pin PORTB<4:0> = digital Connect CCP2 internally to RB3 pin Brown-out reset controlled by software Brown-out voltage set for 2.1V, nominal Deselect low-power Timer1 oscillator // Define PIC18LF4321 registers and bits
/******************************* * Global variables ******************************* */ unsigned int DELAY; // Counter for obtaining a delay unsigned char ALIVECNT; // Counter for blinking "Alive" LED /******************************* * Constant strings ******************************* */
Section 4.2
45
/******************************* * Variable strings ******************************* */ /******************************* * Function prototypes ******************************* */ void Initial(void); void BlinkAlive(void); void Pushbutton(void); /******************************* * Macros ******************************* */ #define Delay(x) DELAY = x; while(--DELAY){ Nop(); Nop(); } /////// Main program /////////////////////////////////////////////////////////// /******************************* * main ******************************* */ void main() { Initial(); // Initialize everything while (1) { PORTCbits.RC2 = !PORTCbits.RC2; // Toggle pin, for measuring loop time BlinkAlive(); // Blink "Alive" LED Pushbutton(); // Turn on LED while pushbutton is pressed Sleep(); // Sleep, letting watchdog timer wake up chip Nop(); } } /******************************* * Initial * * This function performs all initializations of variables and registers. ******************************* */ void Initial() { OSCCON = 0b01100010;
46
Chapter 4
ADCON1 = 0b00001011; TRISA = 0b00001111; TRISB = 0b01000100; TRISC = 0b10000000; TRISD = 0b10000100; TRISE = 0b00000010; PORTA = 0; PORTB = 0; PORTC = 0; PORTD = 0b00100000; PORTE = 0; Delay(50000); RCONbits.SBOREN = 0; ALIVECNT = 247; WDTCONbits.SWDTEN = 1; }
// // // // // // //
RA0,RA1,RA2,RA3 pins analog; others digital Set I/O for PORTA Set I/O for PORTB Set I/O for PORTC Set I/O for PORTD Set I/O for PORTE Set initial state for all outputs low
// except RD5 that drives LCD interrupt // // // // Pause for half a second Now disable brown-out reset Blink immediately Enable watchdog timer
/******************************* * BlinkAlive * * This function briefly blinks the LED every four seconds. * With a looptime of about 16 ms, count 250 looptimes ******************************* */ void BlinkAlive() { PORTDbits.RD4 = 0; if (++ALIVECNT == 250) { ALIVECNT = 0; PORTDbits.RD4 = 1; } }
// Turn off LED // Increment counter and return if not 250 // Reset ALIVECNT // Turn on LED for 16 ms every 4 secs
/******************************* * Pushbutton * * This function overrides the role of the BlinkAlive function and turns on * the LED for the duration of a pushbutton press. ******************************* */ void Pushbutton() { PORTEbits.RE0 = 1; Nop(); if (!PORTDbits.RD7) { PORTDbits.RD4 = 1; } PORTEbits.RE0 = 0; }
// Power up the pushbutton // Delay one microsecond before checking it // If pressed // turn on LED // Power down the pushbutton
Section 4.3
Configuration Selections
47
name main is used by C programs to indicate to the C compiler where program execution is to begin. The line
#include <p18f4321.h>
is needed by the C compiler to assign addresses to register names like OSCCON, TRISA, and WDTCON. Also listed in the p18f4321.h file are the bit numbers associated with bit names like RC2, SBOREN, and SWDTEN. For example, SWDTEN is the name of bit 0, the least-significant bit of the WDTCON register. Because the compiler knows SWDTEN is the name of bit 0, it is not necessary for the user to know it. Knowing the names of bits and the registers in which they reside is sufficient.
PWRT
= ON = OFF
Introduces a delay of about 66 ms after the chip detects that power has been turned on and before CPU clocking begins. No 66 ms delay.
48
Chapter 4
CCP2MX
= RB3 = RC1
CCP2 input/output is multiplexed with the RB3 pin. CCP2 input/output is multiplexed with the RC1 pin.
LPT1OSC
= ON = OFF
Timer1 oscillator is configured for low-power, 32768 Hz operation. Timer1 oscillator is configured for higher-power, higher frequency operation.
DEBUG
= ON = OFF
Background debugger is enabled - needed by QwikBug. Background debugger is disabled; RB6 and RB7 available for I/O.
LVP
= OFF = ON
This choice is needed for normal, fast start from reset. This choice can cause a delay of several seconds coming out of reset.
(f) In-circuit serial programming (ICSP) option that permits programming voltage = VDD
MCLRE
= ON = OFF
RE3/MCLR pin is an active dedicated active-low master reset input. RE3/MCLR pin is a general purpose RE3 I/O pin.
PBADEN
= DIG = ANA
PORTB bits 4,3,2,1,0 are configured as digital I/O pins at reset. PORTB bits 4,3,2,1,0 are configured as analog input pins at reset.
FIGURE 4-2 (continued) The OSC choice of INTIO1 selects the primary, power-on reset, oscillator for the chip. This choice can be overridden at any time by the user program by changing the content of the OSCCON register (see Figure 2-3). The brownout-reset options are described in Figure 4-3. The original intent of a brownout reset was to stop the clocking of the CPU when VDD drops below a specified threshold level, as when a power switch is opened. Here, the brownout-reset mechanism is used at startup, to hold the chip in the reset state until sometime after the power switch connects the coin cell to the VDD line supplying the MCU. With BORV = 3 and with the power-up timer enabled with PWRT = ON, clocking of the CPU begins about 66 ms after VDD rises above about 2.1 V. Should the power
Section 4.3
Configuration Selections
49
2.1V 2.0V
1.0V
Time
OFF
OFF
ON
OFF
OFF
ON
PWRT delay (66 ms) (a) Power-on behavior of brownout reset BOR = SOFT Brownout reset controlled by SBOREN bit in RCON register and enabled at reset = OFF Brownout reset disabled (0 A current draw) = ON Brownout reset enabled (34 A current draw) = NOSLP Brownout reset in run and idle modes; disabled in sleep mode (b) Brownout-reset configuration SBOREN =1 =0 Brownout feature enabled Brownout feature disabled
(c) Software control of the brownout-reset feature when BOR = SOFT (SBOREN is bit 6 of the RCON register)
50
Chapter 4
switch exhibit contact bounce, a reliable startup will ensue, even after one or more false starts. With BOR = SOFT, the brownout feature can be disabled after startup by clearing the SBOREN bit in the RCON register to eliminate its current draw of about 34 A.
will set the SWDTEN bit in the WDTCON register. For testing or manipulating a bit of a variable, the C18 compiler does not provide the same support. Thus
ALIVECNTbits.7 = 0;
will generate a compiler error rather than generating code that will clear bit 7 of the RAM variable, ALIVECNT. When writing code for a microcontroller, a commonly recurring need arises for flag bits that can be set, cleared, and tested. Because the PIC18LF4321 has 512 bytes of RAM available, dedicating some of these to serve as two-valued flags is not unreasonable. Thus, in the template program of the next chapter, a char (8-bit) variable named PBFLAG is introduced to distinguish between operation before the pushbutton is first pressed and subsequently. Before the pushbutton is first pressed, PBFLAG is cleared to zero with the line
PBFLAG = 0;
and the display shows the message PRESS PB After the pushbutton is pressed, PBFLAG is set to one with
PBFLAG = 1;
Section 4.7
51
and the display switches to its ongoing program use. The flag is tested with
if (!PBFLAG) { <do these tasks before pushbutton is first pressed> }
or with
if (PBFLAG) { <do these tasks if pushbutton has already been pressed> }
The pushbutton can be powered up, as shown in Figure 3-2, by setting RE0. Then RD7 can be read to set NEWPB if RD7 is low (i.e., if the pushbutton is pressed)
NEWPB = !PORTDbits.RD7;
This flag is compared with the value of NEWPB found some time earlier and saved in OLDPB with
if (!OLDPB && NEWPB)// Look for last time = 0, now = 1 { <do these tasks if pushbutton is newly pressed> }
52
Chapter 4
This tells the C18 compiler that, when it subsequently sees the character sequence:
Delay(50000)
The compiler will generate code that, when executed, will load 50,000 into the unsigned int variable, DELAY, then decrement DELAY. If the decremented value of DELAY equals zero, then the execution of the macro is done. Otherwise two Nop() macros are executed before DELAY is decremented again. Each Nop() macro is compiled to a no operation assembly language instruction. What is interesting here is the relationship between the parameter value and the duration of the resulting delay. The insertion of the line
Delay(value); (4-1a)
will create a delay in the program execution of exactly delay = (10 value) clock periods for value < 256 or delay = (10 value) + 1 clock periods for value 256 Ignoring the additional clock period for value 256 and the 2% accuracy of the internal clock, this macro can be used to generate a calibrated delay. With FOSC = 4 MHz, the CPU clock period equals one microsecond and the delay will equal delay = 10 value microseconds given the global variable declaration
unsigned int DELAY;
(4-1b)
(4-1c)
In general, it is difficult to predict how the C18 compiler will optimize the code of a program. Alex Singh discovered that without the inclusion of any assembly language code within the Delay() macro, it would be compiled in three different ways in different source files (optimized for speed of execution, optimized for minimal code generation, or not optimized at all). However, with the inclusion of any assembly code in a macro definition, the macro is always compiled to the same machine code.
Section 4.7
53
selects FOSC = 4 MHz and a CPU clock rate of FCPU = 1 MHz. Since most instructions are executed in one CPU clock period, this means that a sequence like
PORTBbits.RB0 = 1; PORTBbits.RB0 = 0;
will generate a 1-s positive pulse on the RB0 pin. Sometimes a short pause is required between the activation of a process and the reading of the output of the process. Inserting the macro
Nop();
can be used to insert a pause of 1 s in the execution of the code as the CPU executes a single-cycle no operation machine instruction. The decision to select FOSC = 4 MHz rather than the higher value of 8 MHz is driven largely by the data of Figure 2-4. This illustrates that while the MCU is awake and running with FOSC = 8 MHz, it draws 1.750 mA, a heavy current for the coin cell while undebugged code leaves the chip constantly awake. The choice of FOSC = 4 MHz drops this steady current draw of a malfunctioning program to a milliampere, considerably better. For intermittent sleep mode operation with FOSC = 4 MHz, an application suffers an average current draw penalty of 6.6 5.6 _______ 100 = 18% 5.6 relative to the average current draw with FOSC = 8 MHz.
54
Chapter 4
The choice of FOSC = 4 MHz versus an even slower clock rate is driven by Figure 2-8, given that the applications discussed in this book will usually operate in the intermittent sleep mode. When such is not the case, the slow INTRC internal oscillator of Figure 2-3 or the slow Timer1 crystal oscillator of Figure 3-3 will present an excellent alternative for applications that can deal with the slow execution of a CPU clock that executes only about eight machine instructions every millisecond.
sets up bits 7 and 2 of PORTD as inputs and bits 6, 5, 4, 3, 1, and 0 as outputs. The pins that are unused by the board are indicated as such in Figure 3-7. These are RD0 and RD1, also set up as outputs. All of the PORTD output port pins are initialized to zero except for RD5 that is set to one via the line
PORTD = 0b00100000;
A 10 transition from this output pin will be used to wake up the LCD controller. For now it is left to idle high.
Section 4.12
Main Loop
55
7 1 6 5 4 0 3 2 1 0 TRISC PORTC
1 k
7 6 5 4 3 2 0 1 0
Oscilloscope
RC2
56
Chapter 4
function is called during each pass around the main loop. Each call occurs approximately 16 ms after the previous one, so every 250 16 ms = 4,000 ms = 4 s the ALIVECNT variable will have been incremented to 250, reset to zero, and the LED driven from RD4 will be turned on. It remains on until 16 ms later when BlinkAlive is again called and RD4 is cleared with the opening statement of BlinkAlive,
PORTDbits.RD4 = 0;
Note this use by the C compiler of the term PORTDbits rather than the term PORTD when accessing a specific bit (RD4) in a register (PORTD). The main function closes with a Sleep macro that is translated by the C compiler into the PIC18LF4321s sleep instruction. Upon awakening from sleep, the CPU may not carry out the operation of the next instruction correctly. By having that next instruction be a nop instruction, no intended operation is passed over. Consequently, a Sleep macro should always be followed by a Nop macro (translated to the chips no operation one cycle instruction).
4.15 COMPILATION
Set up a folder C:\WORK to hold source files (e.g., T1.c) as well as the files generated as a result of the compilation of source files. In addition, it is useful to have a desktop icon that opens into this folder and another desktop icon that opens a DOS window into this folder. For Windows XP, the www.qwikandlow.com website has a batch file MakeWork.bat and two desktop short cuts Work DOS for work that can be downloaded to the readers desktop. The batch file creates a new folder C:\Work Clicking on the Work desktop icon opens the C:\Work folder. Clicking on the DOS for Work desktop icon opens a DOS window with a C:\Work> prompt. Download from www.qwikandlow.com into the new C:\Work folder the batch file C18.exe and the source file T1.c
Section 4.15
Compilation
57
Finally, install the student version of Microchips C18 compiler including their pathlist settings. This can be found on the Microchip website by Googling +MPLAB C18 compiler +Student Edition To try compilation, click on the DOS for Work icon. Then after the C:\Work> prompt, type C18 T1 To edit the T1.c file, any text editor can be used. The Crimson editor is a popular and free one, available from www.crimsoneditor.com. It understands C and it flags syntax errors.
PROBLEMS
4-1 Faster blinking Modify the T1.c file into T1faster.c so as to blink the LED every second. Recompile, download, and run the result. 4-2 Pushbutton modification Form a T1pb.c file in which the BlinkAlive function and its call are removed from the file. Modify the Pushbutton function so that it blinks on for only 16 ms in response to each pushbutton press. 4-3 Another pushbutton modification Form a T1pb2.c file. In response to each pushbutton press, blink the LED twice. Each blink should last for one loop time (i.e., about 16 ms). The duration between blinks should be 32 loop times (i.e., about 0.5 second). Make sure that the MCU sleeps between loop times. 4-4 Measurements For each of the above programs, make two measurements. a) Measure the current draw with the LED jumper removed. Is there any measurable difference between the current draw for these programs? b) Probe the MCUs CPU clock, FOSC/4 at test point TP6. Referring to Figure 2-5, measure both Tperiod and the maximum value in each case of Tactive. How do these compare between the programs? 4-5 Oscillator Control For this project, you will carry out the eight INTOSC clock source tests of Figure 2-8. However, instead of executing the 100 clock periods called for there, just execute enough code to switch the oscillator frequency to the next value in response to a pushbutton press. Initialize OSCCON to 0b01110010 and OSCTUNE to 0b10000000. This will produce the conditions for measuring PCPU, Texec, and Iavg for the first row of the table of Figure 2-8. Each of the seven pushes of the pushbutton will yield the conditions for the remaining rows (ignoring the row for the INTRC clock source). To change OSCCON just once for each pushbutton press, define and use the two flag variables NEWPB and OLDPB discussed at the end of Section 4.5.
Chapter
58
Section 5.2
59
The MCU signals the LCD controller to wake up with a one to zero falling edge from its RD5 output pin to the LCD controllers INT0 interrupt input pin. Upon reception of this falling edge, the LCD controllers CPU awakens to receive a string of characters from the MCU, interpret them into their 14-segment starburst representation, and load the results into LCD data registers before returning to sleep. While the LCD controllers CPU sleeps, its LCD module refreshes the LCD display at a 37 Hz refresh
VDD 4PDT switch VCC
VDD MCU PIC18LF4321 RD5 INT0 SCK SPI master SDO SDI GND SCK SPI slave
GND
(a) Connections between MCU and LCD controller Wake up LCD controller RD5 Clear flag SSPIF f lag SCK Flag set automatically upon completion of transfer
SDO
bit7
bit6
bit5
bit4
bit3
bit2
bit1
bit0
60
Chapter 5
TRISC
0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0
SSPIF = SSPBUF
A write to SSPBUF initiates a transfer (most-significiant bit first) (c) MCUs SPI registers
FIGURE 5-1 (continued) rate. It is this combination of sleeping CPU, very slow refresh rate, and low capacitance loading by the LCD pins that produces the small 5-A current draw of the LCD and its controller when it is not being updated by a display string from the MCU. Figure 5-1b also illustrates the role of the MCUs SSPIF flag that is set upon the completion of the 1-byte transfer. To send a sequence of bytes, the SSPIF flag is cleared, the first byte of the sequence is written to the SSPBUF register, and program execution waits until the SSPIF flag is set before clearing SSPIF and writing the next byte to SSPBUF. With each 1-byte transfer taking just the 8 s dictated by an SCK clock output that consists of eight pulses of the MCUs FOSC/4 CPU clock, this interface helps to minimize the awake time of both the MCU when it deals with the display and the LCD controller when it awakens to receive an update. The MCUs SPI registers and their initialization to produce the waveform of Figure 5-1b are illustrated in Figure 5-1c. The SPI has many options: Whether the SPI module drives SCK (master mode) or uses SCK as a clock input (slave mode). Whether the SCK pin idles high (as in Figure 5-1b) or low. Whether it uses its fastest clock rate of Fosc/4 or a slower rate. For the connection of Figure 5-1a to function properly, it is important for the MCUs SPI to be set up as master and the LCD controllers SPI to be set up as slave. It is also important for the MCU and the LCD controller to agree on the polarity of the SCK pulses. With its use of FOSC = 8 MHz, the LCD controller can accept SPI inputs at any of the MCUs SPI clock rates. The initialization of SSPSTAT and SSPCON1 shown in Figure 5-1c produces the waveforms of Figure 5-1b and produces the idlehigh SCK that the LCD controller expects.
Section 5.3
Display Strings
61
Wake up LCD controller RD5 SSPIF flag SDO Clear SSPIF Write to SSPBUF CH0 CH1 CH2 CH3 CH4 CH5 CH6 CH7 Raise RD5 any time before sending another display string
FIGURE 5-2 Display string format consisting of nine bytes (eight ASCII-coded characters plus an optional ASCII-coded decimal point)
CH0
CH1
CH2
CH3
CH4
CH5
CH6
CH7
(a) LCD display, showing names of character positions ''1 2 3 4 5 6 7 8 '' will produce ''1 2 3 4 1 2 3 4 5 6 7 8 1 2 3 4
5 6 7 8
62
Chapter 5
Turn on all segments for that character position (c) Unaccounted-for 8-bit codes.
Section 5.6
63
with both the 4 and the decimal point in the CHAR3 position. The LCD display on the Qwik&Low board has eight decimal points, one on the right side of each character position. Consequently the display string
".12345678"
will be displayed as .1234567 with the last character (8) ignored. A more readable result will occur by sending
"0.1234567"
/******* T2.c ****************** * * Use Fosc = 4 MHz for Fcpu = Fosc/4 = 1 MHz. * Sleep for 16 ms (nominal), using watchdog timeout for wakeup. * Toggle RC2 output every 16 milliseconds for measuring looptime with scope. * Blink LED on RD4 for 16 ms every four seconds. * Post PRESS PB message on LCD until first pushbutton push. * Increment LCD's CHAR0:CHAR1 every second. * Increment LCD's CHAR3:CHAR4 for each pushbutton press. * * Current draw = 7 uA (with LED and LCD switched off) * ******* Program hierarchy ***** * * main * Initial * Display * BlinkAlive * Time * Pushbutton * UpdateLCD * Display * ******************************* */
64
Chapter 5
#include <p18f4321.h> #include <string.h> /******************************* * Configuration selections ******************************* */ #pragma #pragma #pragma #pragma #pragma #pragma #pragma #pragma #pragma #pragma #pragma config config config config config config config config config config config OSC = INTIO1 PWRT = ON LVP = OFF WDT = OFF WDTPS = 4 MCLRE = ON PBADEN = DIG CCP2MX = RB3 BOR = SOFT BORV = 3 LPT1OSC = OFF
// // // // // // // // // // //
Use internal osc, RA6=Fosc/4, RA7=I/O Enable power-up delay Disable low-voltage programming Disable watchdog timer initially 16 millisecond WDT timeout period, nominal Enable master clear pin PORTB<4:0> = digital Connect CCP2 internally to RB3 pin Brown-out reset controlled by software Brown-out voltage set for 2.1V, nominal Deselect low-power Timer1 oscillator
/******************************* * Global variables ******************************* */ char PBFLAG; // char LCDFLAG; // char NEWPB; // char OLDPB; // unsigned char ALIVECNT; // unsigned char TIMECNT; // unsigned char ONES; // unsigned char TENS; unsigned char PBONES; // unsigned char PBTENS; unsigned char i; // unsigned int DELAY; // char LCDSTRING[] = "PRESS PB "; // /******************************* * Function prototypes ******************************* */ void void void void void void Initial(void); BlinkAlive(void); Pushbutton(void); Time(void); UpdateLCD(void); Display(void);
Flag, set after first press of pushbutton Flag, set to send string to display Flag, set if pushbutton is now pressed Flag, set if pushbutton was pressed last loop Scale-of-248 counter for blinking "Alive" LED Scale-of-62 counter of loop times = 1 second For display of seconds For display of pushbutton count Index into strings Sixteen-bit counter for obtaining a delay LCD display string
Section 5.6
65
#define Delay(x) DELAY = x; while(--DELAY){ Nop(); Nop(); } #define LoadLCDSTRING(lit) strcpypgm2ram(LCDSTRING,(const far rom char*)lit) /////// Main program /////////////////////////////////////////////////////////// /******************************* * main ******************************* */ void main() { Initial(); // while (1) { PORTCbits.RC2 ^= 1; // BlinkAlive(); // Time(); // Pushbutton(); // UpdateLCD(); // Sleep(); // Nop(); } }
Initialize everything
Toggle pin, for measuring loop time Blink "Alive" LED Display seconds Display pushbutton count Update LCD Sleep, letting watchdog timer wake up chip
/******************************* * Initial * * This function performs all initializations of variables and registers. ******************************* */ void Initial() { OSCCON = 0b01100010; // Use Fosc = 4 MHz (Fcpu = 1 MHz) SSPSTAT = 0b00000000; // Set up SPI for output to LCD SSPCON1 = 0b00110000; ADCON1 = 0b00001011; // RA0,RA1,RA2,RA3 pins analog; others digital TRISA = 0b00001111; // Set I/O for PORTA TRISB = 0b01000100; // Set I/O for PORTB TRISC = 0b10000000; // Set I/O for PORTC TRISD = 0b10000000; // Set I/O for PORTD TRISE = 0b00000010; // Set I/O for PORTE PORTA = 0; // Set initial state for all outputs low PORTB = 0; PORTC = 0; PORTD = 0b00100000; // except RD5 that drives LCD interrupt PORTE = 0; SSPBUF = ' '; // Send a blank to initialize state of SPI Delay(50000); // Pause for half a second RCONbits.SBOREN = 0; // Now disable brown-out reset PBFLAG = 0; // Clear flag until pushbutton is first pressed LCDFLAG = 0; // Flag to signal LCD update is initially off TIMECNT = 0; // Reset TIMECNT TENS = '5'; // Initialize to 59 so first display = 00 ONES = '9';
66
Chapter 5
PBTENS = '0'; PBONES = '1'; OLDPB = 0; ALIVECNT = 247; WDTCONbits.SWDTEN = 1; Display(); LoadLCDSTRING("00 01 }
// Initialize count of pushbutton presses // // // // // Initialize to unpressed pushbutton state Blink immediately Enable watchdog timer Display initial "PRESS PB" message Reinitialize LCDSTRING
");
/******************************* * BlinkAlive * * This function briefly blinks the LED every four seconds. * With a looptime of about 16 ms, count 4x62 = 248 looptimes ******************************* */ void BlinkAlive() { PORTDbits.RD4 = 0; // Turn off LED if (++ALIVECNT == 248) // Increment counter and return if not 248 { ALIVECNT = 0; // Reset ALIVECNT PORTDbits.RD4 = 1; // Turn on LED for 16 ms every 4 secs } } /******************************* * Time * * After pushbutton is first pushed, display seconds. ******************************* */ void Time() { if (PBFLAG) // After pushbutton is first pushed, { if (++TIMECNT == 62) // count TIMECNT to 1 second { TIMECNT = 0; // Reset TIMECNT for next second if (++ONES > '9') // and increment time { ONES = '0'; if (++TENS > '5') { TENS = '0'; } } LCDSTRING[0] = TENS; // Update display string LCDSTRING[1] = ONES; LCDFLAG = 1; // Set flag to display } } }
Section 5.6
67
/******************************* * Pushbutton * * After pushbutton is first pressed, display pushbutton count. ******************************* */ void Pushbutton() { PORTEbits.RE0 = 1; // Power up the pushbutton Nop(); // Delay one microsecond before checking it NEWPB = !PORTDbits.RD7; // Set flag if pushbutton is pressed PORTEbits.RE0 = 0; // Power down the pushbutton if (!OLDPB && NEWPB) // Look for last time = 0, now = 1 { if (!PBFLAG) // Take action for very first PB press { PBFLAG = 1; ALIVECNT = 0; // Synchronize LED blinking to counting TIMECNT = 61; // Update display immediately } else // Take action for subsequent PB presses { if (++PBONES > '9') // and increment count of PB presses { PBONES = '0'; if (++PBTENS > '9') { PBTENS = '0'; } } } LCDSTRING[3] = PBTENS; // Update display string for simulated LCD LCDSTRING[4] = PBONES; LCDFLAG = 1; // Set flag to display } OLDPB = NEWPB; // Save present pushbutton state } /******************************* * UpdateLCD * * This function updates the 8-character LCD once the pushbutton has * first been pressed, if Time or Pushbutton has set LCDFLAG. ******************************* */ void UpdateLCD() { if(PBFLAG && LCDFLAG) { Display(); LCDFLAG = 0; } }
68
Chapter 5
/******************************* * Display() * * This function sends LCDSTRING to the LCD. ******************************* */ void Display() { PORTDbits.RD5 = 0; // Wake up LCD display for (i = 0; i <= 8; i++) { PIR1bits.SSPIF = 0; // Clear SPI flag SSPBUF = LCDSTRING[i]; // Send byte while (!PIR1bits.SSPIF); // Wait for transmission to complete } PORTDbits.RD5 = 1; // Return RB5 high, ready for next string }
Section 5.11
69
by the UpdateLCD function after the pushbutton has first been pressed. Thereafter, it is called by the UpdateLCD function to display the elapsed seconds and to update the number of pushbutton presses. Before the Display function is called, LCDSTRING must be loaded with the nine ASCII-coded characters to be sent to the display. The T2.c template program does this in several ways. In the Global variables section, the line
char LCDSTRING[] = "PRESS PB "; // LCD display string
both defines LCDSTRING as a char array and initializes it to contain the nine characters between the quotes. At the end of the Initial function, the Display function sends this message string to the display and then uses a LoadLCDSTRING macro to reinitialize LCDSTRING with the initial values of the numbers that will be displayed subsequently, when the Time function and the Pushbutton function update individual characters in LCDSTRING. When the string of characters is sent to the display, the sequence of events shown in Figure 5-2 occur. The Display function begins by dropping RD5 to awaken the LCD controller. It sends the nine characters to the display via the SPI bus. After clearing the SPIs SSPIF flag, each character is written to the SPIs SSPBUF register. The function then waits for the completion of the transfer (signaled by the setting of the SSPIF flag) before sending the next character. After all characters have been sent, RD5 is raised, ready for the next call of Display.
and
LCDSTRING[1] = UNITS;
insert the ASCII characters stored in the TENS and ONES variables into the first 2 bytes of LCDSTRING. The lines leading up to these lines increment the two-digit ASCIIcoded number in TENS:ONES from 00 to 59 and back to 00. By counting 62 loop times in TIMECNT between each increment of TENS:ONES, the displayed time is incremented every second (within the accuracy of the watchdog timers nominal 16 ms timeout period). The test of PBFLAG maintains the initial startup message on the display PRESS PB until the first press of the pushbutton. The setting of the LCDFLAG at the end of the Time function is used to signal the UpdateLCD function that LCDSTRING has been changed and that the LCD should be updated accordingly.
70
Chapter 5
a pushbutton press, it sets LCDFLAG, just as was done by the Time function. Consequently, whenever either a 1-s tick or a pushbutton press occurs, the UpdateLCD function will update the display during that same pass around the main loop. To understand how the MCU detects a pushbutton press, refer back to the circuit of Figure 3-2. RE0 is first raised. A 1-s pause is introduced by the Nop() macro to allow time to change whatever capacitance is associated with the RE0 trace on the Qwik&Low board. If the pushbutton is pressed, the RD7 input will be read as a zero and the line
NEWPB = !PORTDbits.RD7;
will put a nonzero value into the NEWPB byte serving as a flag. If the pushbutton is not pressed, RD7 will be read as a one and NEWPB will be zero. The state of the pushbutton one loop time (i.e., 16 ms) ago is held in OLDPB. The combined condition
!OLDPB && NEWPB
detects the beginning of a keypress. Keybounce has been suppressed by the loop time sampling of the keyswitch state, as per Figure 2-7. Before the first press after reset, PBFLAG will equal zero. Accordingly, when the first press occurs, PBFLAG will be set and the initialization of ALIVECNT and TIMECNT will occur just for this initial press. The ASCII values for zero and one initialized into PBTENS and PBONES are copied into LCDSTRING[3] and LCDSRING[4] and LCDFLAG is set, to signal the UpdateLCD function to overwrite the PRESS PB message with a time of 00 and a number of keypresses of 01. On subsequent keypresses, the ASCII values held in PBTENS and PBONES are incremented and then displayed by the UpdateLCD function.
PROBLEMS
5-1 Initial message Change the initial message from PRESS PB to WELCOME. 5-2 Relocation of display elements a) Move the elapsed time to the CHAR1:CHAR2 position on the display. b) Move the count of pushbutton presses to the CHAR5:CHAR6 position on the display. 5-3 Blast off counter Change the display of elapsed time to count down from an initial value of 10. When zero is reached: a) Fill the screen with eight asterisks and stop further updating of the display. b) Blank the screen. Twelve loop times later write asterisks to the middle two character positions. After another 12 loop times, write asterisks to the middle four character positions. After another 12 loop times, write asterisks to the middle six character positions. Finally, after another 12 loop times, fill the screen with eight asterisks and stop any further updating of the display.
Chapter
72
Chapter 6
times in units of microseconds will vary from board to board because of variations in the 2% accurate internal clock frequency.
Section 6.2
One frame
Idle bits
Idle bits
8 data bits
Start bits
Stop bits
Idle-to-start transition
Stop-to-start transition
Receiver synchronizes on Idle-to-start transition and resynchronizes on each subsequent stop-to-start transition.
73
74
One frame
stop bit
start bit
TX 16 24 32 128 136
01
144
152
160
one tick
Chapter 6
Section 6.2
75
between FOSC, baud rate, BRGH, and SPBRG is shown in Figure 6-3b. Using the MCUs INTOSC internal oscillator, the 19,200-baud rate of the PC can be approximated by any of the four settings of Figure 6-3c, depending upon the Fosc value selected. The frequency error of the INTOSC oscillator is specified to be less than 2% at 25C (i.e., 77F) over the full supply voltage range of 2.0 V to 5.5 V. This error plus the 0.16% baud-rate error of Figure 6-3c are comfortably less than the 4.60% accuracy required by the PCs UART.
SPBRG N Equal
TXSTA BRGH
Comparator
FOSC
BRGH = 1
FOSC = 4 (SPBRG + 1) Baud rate FOSC = 16 (SPBRG + 1) Baud rate (b) Baud rate derivation from FOSC
BRGH = 0
BRGH
SPBRG
0 0 1 1
25 12 25 12
76
Chapter 6
TXREG Automatically transfer TXREG to TSR when TSR has been emptied
Stop bit = 0 0
TXSTA x x x x x x
TRMT flag x
Section 6.4
UART Initialization
77
TRISC x 0 x x x x x x TX/RC6 configured as an output BAUDCON 0 1 1 1 1 0 0 0 TXCKP RCSTA 1 x x x x x x x SPEN TXSTA 0 0 1 0 0 0 0 TRMT 1: 0: 1: 0: Flag indicates TSR is empty Flag indicates TSR is transmitting Transmit function is enabled Transmit function is disabled 1: 0: Serial port is enabled Serial port is disabled 1: 0: TX data is inverted TX data is not inverted
SPBRG PIR1 x x x
12 x x x x
Baud-rate generator for 19200 baud with FOSC = 4 MHz (see Figure 6-3c)
TXIF TXREG
1: 0:
/******************************* * InitTX * * This function initializes the UART for its TX output function. It assumes * Fosc = 4 MHz. For a different oscillator frequency, use Figure 6-3c to * change BRGH and SPBRG appropriately. ******************************* */ void InitTX() { RCSTA = 0b10010000; TXSTA = 0b00100000; SPBRG = 12; BAUDCON = 0b00111000; }
// // // //
(b) Initialization
78
Chapter 6
Even though QwikBug has already initialized the UART in order to download a user program, run it, and aid in debugging it, QwikBug has done so with FOSC = 8 MHz. For a user program operating with FOSC = 4 MHz, the baud rate settings must be reinitialized to the settings shown in Figure 6-5 in order to have the PC accept the MCU output correctly at 19,200 baud. QwikBug handles these shared registers with care, saving user contents on entering QwikBug at a breakpoint or after a single step, and restoring the user contents on exiting back to the user program.
That is, each of these variables began with the ASCII code for zero. Thereafter, these values were updated by incrementing to the next ASCII code or by resetting to '0'.
#define TXascii(in) TXREG = in; while(!TXSTAbits.TRMT) (a) TXascii macro definition
// Display ASCII-coded content of HUNDREDS // Display the letter A // Carriage return // Line feed // Display the letter A // Carriage return // Line feed
Section 6.6
Number-to-ASCII Conversion
79
More generally, a number will be obtained as a result of a measurement and will need to be converted to ASCII-coded char variables: ONES TENS HUNDREDS THOUSANDS etc.
ready for display. In this section, two algorithms will be considered. The first breaks out the digits, most-significant-digit first, by successive subtractions. The second breaks out the digits, least-significant-digit first, by successive divisions. For each of these algorithms, two versions will be developed. ASCII and ASCIID convert NUMBER, a value ranging from 0 to 255, with the functions shown in Figure 6-7. The first line of ASCII initializes the three output variables to 0. The second line forms HUNDREDS by repeatedly subtracting 100 from NUMBER until NUMBER is less than 100. The third line forms TENS by repeatedly subtracting 10 from what remains in NUMBER. The fourth line is reached with NUMBER having a value ranging between zero and nine. This value is added to the ASCII-coded zero initialized into ONES. In Figure 6-8, ASCII4 and ASCII4D operate on BIGNUM, the int version of NUMBER by adding the extra lines of code needed to generate one more digit. Although numbers up to 65,535 can be held in BIGNUM, restricting the conversion to any four-digit number up to 9,999 will serve the needs that arise in this book.
Global variables: unsigned char NUMBER; unsigned char HUNDREDS,TENS,ONES; Function prototypes: void ASCII(void); void ASCIID(void); (a) Definitions // Eight-bit number to be converted // ASCII coding of digits
/******************************* * ASCII * * This function converts the unsigned char parameter passed to it * in NUMBER, ranging from 0 to 255, to three ASCII-coded digits * by performing successive subtractions. * Simplified by Chad Kersey. Takes up to 98 cycles. ******************************* */ void ASCII() { ONES = TENS = HUNDREDS ='0'; //Initialize to ASCII zeroes while (NUMBER >= 100) { HUNDREDS++; NUMBER -= 100; } // Form HUNDREDS
FIGURE 6-7 Conversion of the char variable NUMBER ranging from 0 to 255
80
Chapter 6
(b) ASCII for conversion by successive subtractions /******************************* * ASCIID * * This function converts the unsigned char parameter passed to it * in NUMBER, ranging from 0 to 255, to three ASCII-coded digits * by performing successive divisions. Takes up to 357 cycles. ******************************* */ void ASCIID() { ONES = '0' + (NUMBER % 10); // Form ONES NUMBER = NUMBER / 10; TENS = '0' + (NUMBER % 10); // Form TENS HUNDREDS = '0' + (NUMBER / 10); // Form HUNDREDS } (c) ASCIID for conversion by successive divisions
represent worst-case values (i.e., values that produce the most cycles) for ASCII and ASCII4, the successive-subtraction algorithms. They are reasonable values for estimating worst-case cycle counts for the successive-division algorithms. Determining the actual worst-case cycle count for each of the two successive-division algorithms is left as end-of-chapter problems. The resulting numbers of cycle counts are listed in the header of the Measure.c template. The successive-subtraction algorithms require, in the worst case, about a quarter of the number of cycles of the successive-division algorithms. Furthermore, the successive-subtraction algorithms produce cycle counts that are proportional to the sum of the digits in the result, and can thus produce a significantly reduced cycle count in a specific case. For these reasons, ASCII and ASCII4 are used throughout the rest of the book whenever a conversion is needed.
Section 6.7
81
Global variables: unsigned int BIGNUM; // Ranges from 0 to 9999 unsigned char THOUSANDS,HUNDREDS,TENS,ONES; // ASCII coding of digits Function prototypes: void ASCII4(void); void ASCII4D(void); (a) Definitions /******************************* * ASCII4 * * This function converts the unsigned int parameter passed to it * in BIGNUM, ranging from 0 to 9999, to four ASCII-coded digits * by performing successive subtractions. * Simplified by Chad Kersey. Takes up to 353 ******************************* */ void ASCII4() { ONES = TENS = HUNDREDS = THOUSANDS ='0'; //Initialize to ASCII while (BIGNUM >= 1000) { THOUSANDS++; BIGNUM -= 1000; } // Form while (BIGNUM >= 100) { HUNDREDS++; BIGNUM -= 100; } // Form while (BIGNUM >= 10) { TENS++; BIGNUM -= 10; } // Form ONES += BIGNUM; // Form } (b) ASCII4 for conversion by successive subtractions /******************************* * ASCII4D * * This function converts the unsigned int parameter passed to it * in BIGNUM, ranging from 0 to 9999, to four ASCII-coded digits * by performing successive divisions. Takes up to 1498 cycles. ******************************* */ void ASCII4D() { ONES = '0' + (BIGNUM % 10); // Form ONES BIGNUM = BIGNUM / 10; TENS = '0' + (BIGNUM % 10); // Form TENS BIGNUM = BIGNUM /10; HUNDREDS = '0' + (BIGNUM % 10); // Form HUNDREDS THOUSANDS = '0' + (BIGNUM / 10); // Form THOUSANDS } (c) ASCII4D for conversion by successive divisions
cycles.
FIGURE 6-8 Conversion of the int variable BIGNUM ranging from 0 to 9999
82
Chapter 6
/******* Measure.c ************* * * A number between 0 and 9999 is converted to ASCII-coded digits two ways: * ASCII4 forms each digit by successive subtractions (up to 353 cycles). * ASCII4D forms each digit via two divisions (up to 1498 cycles). * Then a number between 0 and 255 is converted to ASCII-coded digits two ways: * ASCII forms each digit by successive subtractions (up to 98 cycles). * ASCIID forms each digit via two divisions (up to 357 cycles). * For each one, the result is displayed on the LCD. * The execution time (cycles)is displayed on the PC * Execution stops with a sleep command. * * Start and Stop functions are added to measure the execution time of the * code between them. The Send function sends the time to the PC monitor. * * Use Fosc = 4 MHz for Fcpu = Fosc/4 = 1 MHz. * ******* Program hierarchy ***** * * main * Initial * InitTX * Start * Stop * Send * TXascii * ASCII4 * ASCII4D * ASCII * ASCIID * Display * ******************************* */ #include <p18f4321.h> // Define PIC18LF4321 registers and bits
/******************************* * Configuration selections ******************************* */ #pragma config OSC = INTIO1 // #pragma config PWRT = ON // #pragma config LVP = OFF // #pragma config WDT = OFF // #pragma config WDTPS = 4 // #pragma config MCLRE = ON // #pragma config PBADEN = DIG // #pragma config CCP2MX = RB3 // #pragma config BOR = SOFT // #pragma config BORV = 3 // #pragma config LPT1OSC = OFF //
Use internal osc, RA6=Fosc/4, RA7=I/O Enable power-up delay Disable low-voltage programming Disable watchdog timer initially 16 millisecond WDT timeout period, nominal Enable master clear pin PORTB<4:0> = digital Connect CCP2 internally to RB3 pin Brown-out reset controlled by software Brown-out voltage set for 2.1V, nominal Deselect low-power Timer1 oscillator
Section 6.7
83
/******************************* * Global variables ******************************* */ unsigned int DELAY; // Sixteen-bit counter for obtaining a delay unsigned char NUMBER; // Eight-bit number to be converted unsigned int BIGNUM; // Sixteen-bit number to be converted unsigned char THOUSANDS,HUNDREDS,TENS,ONES; // ASCII coding of digits unsigned char i; // Index into strings unsigned int CYCLES; // Result of Timer0 counting cycles char LCDSTRING[] = " "; // Nine-character display string /******************************* * Function prototypes ******************************* */ void Initial(void); void InitTX(void); void Start(void); void Stop(void); void Send(void); void ASCII4(void); void ASCII4D(void); void ASCII(void); void ASCIID(void); void Display(void); /******************************* * Macros ******************************* */ #define Delay(x) DELAY = x; while(--DELAY){ Nop(); Nop(); } #define TXascii(in) TXREG = in; while(!TXSTAbits.TRMT) /////// Main program ////////////////////////////////////////////////////////// /******************************* * main ******************************* */ void main() { Initial(); // Initialize everything InitTX(); // and the UART as well BIGNUM = 9999; Start(); ASCII4(); Stop(); LCDSTRING[0] = THOUSANDS; LCDSTRING[1] = HUNDREDS; LCDSTRING[2] = TENS;
// Convert BIGNUM
84
Chapter 6
LCDSTRING[3] = ONES; Send(); LCDSTRING[4] = '.'; BIGNUM = 9999; Start(); ASCII4D(); Stop(); LCDSTRING[5] = LCDSTRING[6] = LCDSTRING[7] = LCDSTRING[8] = Send(); Display();
// Send this cycle count to PC for display // Verify correct conversions on LCD
Delay(50000); Delay(50000); Delay(50000); Delay(50000) // Two-second pause NUMBER = 199; Start(); ASCII(); Stop(); LCDSTRING[0] = LCDSTRING[1] = LCDSTRING[2] = LCDSTRING[3] = Send();
Takes 98 cycles
LCDSTRING[4] = '.'; NUMBER = 199; Start(); ASCIID(); Stop(); LCDSTRING[5] = LCDSTRING[6] = LCDSTRING[7] = LCDSTRING[8] = Send(); Display(); Sleep(); }
// Send this cycle count to PC for display // Verify correct conversions on LCD // Sleep forever
/******************************* * Initial * * This function performs all initializations of variables and registers. ******************************* */
Section 6.7
85
void Initial() { OSCCON = 0b01100010; SSPSTAT = 0b00000000; SSPCON1 = 0b00110000; ADCON1 = 0b00001011; TRISA = 0b00001111; TRISB = 0b01000100; TRISC = 0b10000000; TRISD = 0b10000000; TRISE = 0b00000010; PORTA = 0; PORTB = 0; PORTC = 0; PORTD = 0b00100000; PORTE = 0; SSPBUF = ' '; Delay(50000); RCONbits.SBOREN = 0; }
// Use Fosc = 4 MHz (Fcpu = 1 MHz) // Set up SPI for output to LCD // // // // // // // // RA0,RA1,RA2,RA3 pins analog; others digital Set I/O for PORTA Set I/O for PORTB Set I/O for PORTC Set I/O for PORTD Set I/O for PORTE Set initial state for all outputs low
// except RD5 that drives LCD interrupt // Send a blank to initialize state of SPI // Pause for half a second // Now disable brown-out reset
/******************************* * InitTX * * This function initializes the UART for its TX output function. It assumes * Fosc = 4 MHz. For a different oscillator frequency, use Figure 6-3c to * change BRGH and SPBRG appropriately. ******************************* */ void InitTX() { RCSTA = 0b10010000; // Enable UART TXSTA = 0b00100000; // Enable TX SPBRG = 12; // Set baud rate BAUDCON = 0b00111000; // Invert TX output } /******************************* * Start * * This function clears Timer0 and ******************************* */ void Start() { T0CON = 0b00001000; // TMR0H = 0; // TMR0L = 0; T0CONbits.TMR0ON = 1; // }
Set up Timer0 to count CPU clock cycles Clear Timer0 Start counting
86
Chapter 6
/******************************* * Stop * * This function stops counting Timer0, and reads the result into CYCLES. ******************************* */ void Stop() { T0CONbits.TMR0ON = 0; // Stop counting CYCLES = TMR0L; // Form CYCLES from TMR0H:TMR0L CYCLES += (TMR0H * 256); CYCLES -= 3; // Remove 3 counts so back-to-back Start-Stop } // functions produce CYCLES = 0 /******************************* * Send * * This function converts CYCLES to four ASCII-coded digits and sends * the result to the PC for display. ******************************* */ void Send() { BIGNUM = CYCLES; // Load ASCII4s input parameter ASCII4(); // Convert TXascii('\r'); // Send carriage return TXascii('\n'); // Send line feed TXascii(THOUSANDS); // Send four-digit number TXascii(HUNDREDS); TXascii(TENS); TXascii(ONES); } /******************************* * Display() * * This function sends LCDSTRING to the LCD. ******************************* */ void Display() { PORTDbits.RD5 = 0; // Wake up LCD display for (i = 0; i <= 8; i++) { PIR1bits.SSPIF = 0; // Clear SPI flag SSPBUF = LCDSTRING[i]; // Send byte while (!PIR1bits.SSPIF); // Wait for transmission to complete } PORTDbits.RD5 = 1; // Return RB5 high, ready for next string }
Section 6.7
87
/******************************* * ASCII * * This function converts the unsigned char parameter passed to it * in NUMBER, that ranges between 0 and 255, to three ASCII-coded digits * by performing successive subtractions. * Simplified by Chad Kersey. Takes a maximum of 98 cycles. ******************************* */ void ASCII() { ONES = TENS = HUNDREDS ='0'; //Initialize to ASCII zeroes while (NUMBER >= 100) { HUNDREDS++; NUMBER -= 100; } // Form HUNDREDS while (NUMBER >= 10) { TENS++; NUMBER -= 10; } // Form TENS ONES += NUMBER; // Form ONES } /******************************* * ASCIID * * This function converts the unsigned char parameter passed to it * in NUMBER, that ranges between 0 and 255, to three ASCII-coded digits * by performing successive divisions. Takes up to 357 cycles. ******************************* */ void ASCIID() { ONES = '0' + (NUMBER % 10); // Form ONES NUMBER = NUMBER / 10; TENS = '0' + (NUMBER % 10); // Form TENS HUNDREDS = '0' + (NUMBER / 10); // Form HUNDREDS } /******************************* * ASCII4 * * This function converts the unsigned int parameter passed to it * in BIGNUM, that ranges between 0 and 9999, to four ASCII-coded digits * by performing successive subtractions. * Simplified by Chad Kersey. Takes a maximum of 353 cycles. ******************************* */ void ASCII4() { ONES = TENS = HUNDREDS = THOUSANDS ='0'; //Initialize to ASCII zeroes while (BIGNUM >= 1000) { THOUSANDS++; BIGNUM -= 1000; } // Form THOUSANDS while (BIGNUM >= 100) { HUNDREDS++; BIGNUM -= 100; } // Form HUNDREDS while (BIGNUM >= 10) { TENS++; BIGNUM -= 10; } // Form TENS ONES += BIGNUM; // Form ONES }
88
Chapter 6
/******************************* * ASCII4D * * This function converts the unsigned int parameter passed to it * in BIGNUM, that ranges between 0 and 9999, to four ASCII-coded digits * by performing successive divisions. Takes up to 1498 cycles. ******************************* */ void ASCII4D() { ONES = '0' + (BIGNUM % 10); // Form ONES BIGNUM = BIGNUM / 10; TENS = '0' + (BIGNUM % 10); // Form TENS BIGNUM = BIGNUM /10; HUNDREDS = '0' + (BIGNUM % 10); // Form HUNDREDS THOUSANDS = '0' + (BIGNUM / 10); // Form THOUSANDS }
PROBLEMS
6-1 ASCIID worst case Modify the Measure.c template to form a MeasureASCIID.c. This program is to run the successive-division algorithm, ASCIID, 256 times with each possible value of NUMBER. Each run is to (possibly) update two int values MIN and MAX and to update a short long (i.e., 24-bit) value SUM. At the conclusion, send MIN and MAX to the QwikBug Console for display. Then form
AVG = (int)(SUM >> 8)
to divide SUM by the 256 trials to get the average number of cycles. Send AVG out for display. 6-2 ASCII worst case Repeat the last problem for the ASCII successivesubtraction algorithm. 6-3 ASCII4D worst case Repeat for the 09,999 cases of the four-digit successive-division algorithm. 6-4 ASCII4 worst case Repeat for the 09,999 cases of the four-digit successivesubtraction algorithm. 6-5 Display execution time Display function. Measure the number of cycles taken to execute the
Chapter
7
REORGANIZATION OF TIMING VIA INTERRUPTS (T3.c)
7.1 OVERVIEW
The code of the template programs to this point has put the MCU to sleep after carrying out the main loop tasks. Then the low-power watchdog timer has awakened the chip every 16 ms to repeat the mainline tasks. In this chapter, an alternative approach for awakening the chip is employed. Now the watchdog timer remains disabled. The MCUs low-power Timer1 oscillator runs continuously and is able to clock its Timer1 counter regardless of whether the remainder of the MCU is asleep or awake. The Timer1 counter is used to control the loop time by awakening the chip periodically with an interrupt. The MCU also has a Timer3 counter, also clocked by the Timer1 oscillator. Tasks requiring faster periodic ticks can easily do so by using Timer3 to produce high-priority interrupts.
90
Chapter 7
has interrupted the main program and the CPU has begun executing the LPISR when a high-priority interrupt source requests service, the CPU automatically suspends the LPISR and executes the HPISR before returning to where it left off in the LPISR. A new template program, T3.c, is introduced that still carries out the tasks of the T2.c template, but with the code reorganized as shown in Figure 7-1. The new main function calls the Initial function where it carries out all of the initialization tasks of T2.c plus the initialization of Timer1 as a low-priority interrupt source and Timer3 as a high-priority interrupt source. Then the CPU puts itself to sleep. Only the Timer1 oscillator with its external 32,768-Hz watch crystal and the Timer1 and Timer3 counters continue to run. The current draw of the MCU drops to 6.5 A. Timer1 is used to produce a 10-ms loop time for all of the main loop tasks. Timer1 is a 16-bit timer that counts from 0 up to 65,535 and then rolls over, back to 0 to
Initial
FIGURE 7-1 Reorganization to use Timer1 for controlling loop time and Timer3 for controlling faster recurring tasks
Section 7.4
Timer1 Oscillator
91
continue counting. The rollover produces a low-priority interrupt. Within the LPISR, Timer1 is reloaded with a number that cuts out all but 328 counts so that the next rollover will occur in 1 1 328counts ______________ ____ s = 10ms 32,768counts/s 100 Timer3 is set up to produce faster tick time interrupts in the same manner. For now, the task is to produce a 1-s-positive pulse every 4 ms on an output pin. The pulse can be monitored with a scope or used to step the stepper motor of Chapter Eight at a rate of 250 steps/s. The sequencing of the two interrupt service routines is illustrated in Figure 7-2. Note how the LPISR is executed every 10 ms, perhaps being briefly extended by a concurrent HPISR.
This choice uses a somewhat higher power driver in its oscillator circuit than is used by the circuit selected with the configuration choice
LPT1OSC = ON
The OFF choice also removes a 3-V regulator from the circuit, intended for use with a higher VDD value. The OFF choice is needed for reliable operation with the Qwik&Low boards 3-V VDD supply. The oscillator, together with its clocking of both Timer1 and Timer3 while the CPU sleeps, draws about 6.5 A. The Qwik&Low board can use the Timer1 oscillator for loop-time control. The crystal oscillator runs even as the rest of the chip sleeps, providing the timing accuracy associated with a 50 parts per million (i.e., 0.005%) crystal. In contrast, use of the
92
CPU asleep
Power up
Main function
Initial function
Low-priority ISR
Chapter 7
High-priority ISR 4 ms 10 ms 4 ms 4 ms 4 ms 10 ms 4 ms 4 ms
Section 7.4
Timer1 Oscillator
93
/******************************* * Interrupt vectors ******************************* */ // For high priority interrupts: #pragma code high_vector=0x08 void interrupt_at_high_vector(void) { _asm GOTO HiPriISR _endasm } #pragma code #pragma interrupt HiPriISR // For low priority interrupts: #pragma code low_vector=0x18 void interrupt_at_low_vector(void) { _asm GOTO LoPriISR _endasm } #pragma code #pragma interruptlow LoPriISR (a) Handling of vectoring to HiPriISR and LoPriISR
/******************************* * HiPriISR ******************************* */ void HiPriISR() { <Tasks to be done> } /******************************* * LoPriISR ******************************* */ void LoPriISR() { <Tasks to be done> } (b) Interrupt service routines themselves
94
Chapter 7
watchdog timer for loop-time control saves $1.35 in parts cost (the cost of the crystal and its two capacitors) and draws only 2.2 A. However, using the watchdog timer Provides limited alternatives for a loop time (i.e., 4-ms minimum or some power of two greater than this). Does so with the much larger error specification of 14%. Does not support fast counting as a side benefit.
Section 7.5
Timer1 Counter
Timer1 oscillator T1OSI and T1OSO pins TMR1L 8-bit counter Frequency = 32768 Hz Period = 30.5 s 32768 Hz crystal 15 pF set PIR1 x x x x x x x TMR1IF
TMR1H
Overflow
8-bit counter
GIEH
GIEL
TMR1IE PIE1 x x x x x x x 1
INTCON 1 1 x x x x x x
TMR1IP IPR1 x x x x x x x 0
95
96
Chapter 7
PROBLEMS
7-1 LoopTime function The testing of the LPISRFLAG in the LoopTime function located at the end of the T3.c program of Figure 7-6 is used to exit from the while loop. Upon entry to the LoopTime function, the CPU immediately goes to sleep. It will remain asleep until either Timer1 or Timer3 causes an interrupt. As part of its execution, Timer1s LPISR will set the LPISRFLAG bit. a) Describe the program flow when the chip is asleep and a Timer3 interrupt occurs. b) Describe the program flow when the chip is asleep and a Timer1 interrupt occurs.
Section 7.7
Timer1 oscillator T1OSI and T1OSO pins TMR3L 8-bit counter Frequency = 32768 Hz Period = 30.5 s 32768 Hz crystal 15 pF set PIR2 x x x x x x x TMR3IF
TMR3H
Overflow
8-bit counter
GIEH
INTCON 1 x x x x x x x
TMR3IP IPR2 x x x x x x 1 x
TMR3IE PIE2 x x x x x x 1 x
97
98
Chapter 7
/******* T3.c ****************** * * Use Fosc = 4 MHz for Fcpu = Fosc/4 = 1 MHz. * Timer1 and Timer3 are both clocked by the Timer1 crystal oscillator. * Same mainline code as for T2.c. * LoopTime function puts chip to sleep. Timer1 awakens chip every 10 ms * within LoopTime function. CPU adjusts Timer1 content for it to timeout * after another ten milliseconds. * Timer3 generates high-priority interrupts every 4 ms to step motor. * Toggle RC2 output every 10 milliseconds for measuring looptime with scope. * Blink LED on RD4 for 10 ms every four seconds. * Post PRESS PB message on LCD until first pushbutton push. * Thereafter, increment and display LCD's CHAR0:CHAR1 every second * and increment and display LCD's CHAR3:CHAR4 for each pushbutton press. * * Current draw = 31 uA (with LED and LCD switched off but whether * or not the stepper motor is connected.) * ******* Program hierarchy ***** * * main * Initial * Display * BlinkAlive * Time * Pushbutton * UpdateLCD * Display * LoopTime * * LoPriISR * * HiPriISR * ******************************* */ #include <p18f4321.h> #include <string.h> // Define PIC18LF4321 registers and bits // Used by the LoadLCDSTRING macro
/******************************* * Configuration selections ******************************* */ #pragma config OSC = INTIO1 // #pragma config PWRT = ON // #pragma config LVP = OFF // #pragma config WDT = OFF // #pragma config WDTPS = 4 // #pragma config MCLRE = ON // #pragma config PBADEN = DIG //
Use internal osc, RA6=Fosc/4, RA7=I/O Enable power-up delay Disable low-voltage programming Disable watchdog timer initially 16 millisecond WDT timeout period, nominal Enable master clear pin PORTB<4:0> = digital
Section 7.7
99
// // // //
Connect CCP2 internally to RB3 pin Brown-out reset controlled by software Brown-out voltage set for 2.0V, nominal Deselect low-power Timer1 oscillator
/******************************* * Global variables ******************************* */ char PBFLAG; // char LCDFLAG; // char NEWPB; // char OLDPB; // char LPISRFLAG; // unsigned int ALIVECNT; // unsigned int STEPCNT; // unsigned char TIMECNT; // unsigned char UNITS; // unsigned char TENS; unsigned char PBUNITS; // unsigned char PBTENS; unsigned char i; // unsigned int DELAY; // char LCDSTRING[] = "PRESS PB "; // /******************************* * Function prototypes ******************************* */ void Initial(void); void BlinkAlive(void); void Time(void); void Pushbutton(void); void UpdateLCD(void); void Display(void); void LoopTime(void); void HiPriISR(void); void LoPriISR(void);
Flag, set after first press of pushbutton Flag, set to send string to display Flag, set if pushbutton is now pressed Flag, set if pushbutton was pressed last loop Flag, set when LP interrupt has been handled Scale-of-400 counter for blinking "Alive" LED 65536 - number of counts between steps Scale-of-100 counter of loop times = 1 second For display of seconds For display of pushbutton count Index into strings Sixteen-bit counter for obtaining a delay LCD display string
/******************************* * Macros ******************************* */ #define Delay(x) DELAY = x; while(--DELAY){ Nop(); Nop(); } #define LoadLCDSTRING(lit) strcpypgm2ram(LCDSTRING,(const far rom char*)lit) /******************************* * Interrupt vectors ******************************* */
100
Chapter 7
// For high priority interrupts: #pragma code high_vector=0x08 void interrupt_at_high_vector(void) { _asm GOTO HiPriISR _endasm } #pragma code #pragma interrupt HiPriISR // For low priority interrupts: #pragma code low_vector=0x18 void interrupt_at_low_vector(void) { _asm GOTO LoPriISR _endasm } #pragma code #pragma interruptlow LoPriISR /////// Main program ////////////////////////////////////////////////////////// /******************************* * main ******************************* */ void main() { Initial(); // while (1) { PORTCbits.RC2 ^= 1; // BlinkAlive(); // Time(); // Pushbutton(); // UpdateLCD(); // LoopTime(); // } }
Initialize everything
Toggle pin, for measuring loop time Blink "Alive" LED Display seconds Display pushbutton count Update LCD Use Timer1 to wakeup and loop again
/******************************* * HiPriISR * * This high-priority interrupt service routine creates a positive pulse on * RD1 every 4 ms. Four milliseconds = 4000 * 0.032768 = 131 Timer3 counts. * Add STEPCNT = 65536+1-131 = 65406 counts to Timer3. * The +1 in the above equation results because the 0-to-1 transition when * Timer3 is reenabled increments Timer3. * RB0 is toggled to measure step period. ******************************* */ void HiPriISR() { T3CONbits.TMR3ON = 0; // Disable clock input to Timer3 TMR3L += STEPCNT; // Add into lower byte
Section 7.7
101
TMR3H = (TMR3H + STATUSbits.C) + (STEPCNT >> 8); // and into upper byte T3CONbits.TMR3ON = 1; // Reenable Timer3 PIR2bits.TMR3IF = 0; // Clear interrupt flag PORTDbits.RD1 = 1; // Create 1 us wide positive pulse PORTDbits.RD1 = 0; // to step motor PORTBbits.RB0 ^= 1; // Toggle pin to measure step period } /******************************* * LoPriISR * * This low-priority interrupt service routine updates Timer1 to interrupt * every 10 ms. Ten ms = 10000 * 0.032768 = 328 to cut out all * but 328 counts. Add 65536+1-328 = 65209 = 0xFEB9 to Timer1 = 0x0000 (or * at most a count or two higher if the HPISR intervenes). ******************************* */ void LoPriISR() { T1CONbits.TMR1ON = 0; // Pause Timer1 counter TMR1L += 0xB9; // Cut out all but 328 counts of Timer1 T1CONbits.TMR1ON = 1; // Resume Timer1 counter TMR1H = 0xFE; // Upper byte of Timer1 will be 0xFE PIR1bits.TMR1IF = 0; // Clear interrupt flag LPISRFLAG = 1; // Set a flag for LoopTime } /******************************* * Initial * * This function performs all initializations of variables and registers. ******************************* */ void Initial() { OSCCON = 0b01100010; // Use Fosc = 4 MHz (Fcpu = 1 MHz) SSPSTAT = 0b00000000; // Set up SPI for output to LCD SSPCON1 = 0b00110000; ADCON1 = 0b00001011; // RA0,RA1,RA2,RA3 pins analog; others digital TRISA = 0b00001111; // Set I/O for PORTA TRISB = 0b01000100; // Set I/O for PORTB TRISC = 0b10000000; // Set I/O for PORTC TRISD = 0b10000000; // Set I/O for PORTD TRISE = 0b00000010; // Set I/O for PORTE PORTA = 0; // Set initial state for all outputs low PORTB = 0; PORTC = 0; PORTD = 0b00100000; // except RD5 that drives LCD interrupt PORTE = 0; SSPBUF = ' '; // Send a blank to initialize state of SPI Delay(50000); // Pause for half a second RCONbits.SBOREN = 0; // Now disable brown-out reset PBFLAG = 0; // Clear flag until pushbutton is first pressed
102
Chapter 7
LCDFLAG = 0; LPISRFLAG = 0; TIMECNT = 0; TENS = '5'; UNITS = '9'; PBTENS = '0'; PBUNITS = '1'; ALIVECNT = 300; STEPCNT = 65406; OLDPB = 0; T1CON = 0b01001111; T3CON = 0b00000111; TMR1H = 0xFE; TMR1L = 0xB9; TMR3H = 0xFF; TMR3L = 0xAE; PIE1bits.TMR1IE = 1; PIE2bits.TMR3IE = 1; IPR1bits.TMR1IP = 0; IPR2bits.TMR3IP = 1; RCONbits.IPEN = 1; INTCONbits.GIEL = 1; INTCONbits.GIEH = 1; Display(); LoadLCDSTRING("00 01 }
// // // //
Flag to signal LCD update is initially off Flag to signal that LPISR has been executed Reset TIMECNT Initialize to 59 so first display = 00
// Initialize count of pushbutton presses // // // // // // // // // // // // // // // // // // Blink immediately Cut out all but 131 counts of Timer3 Initialize to unpressed pushbutton state Timer1 - loop time via low-pri interrupts Timer3 - step motor via hi-pri interrupts Set Timer1 to be 10 ms away from next roll over (65536 + 1 - 328 = 0xFEB9) Set Timer3 to be 4 ms away from next roll over (65536 + 1 - 131 = 0xFF7E) Enable local interrupt source Enable local interrupt source Use Timer1 for low-priority interrupts Use Timer3 for hi-priority interrupts Enable high/low priority interrupt feature Global low-priority interrupt enable Enable both high and low interrupts Display initial "PRESS PB" message Reinitialize LCDSTRING
");
/******************************* * BlinkAlive * * This function briefly blinks the LED every four seconds. * With a looptime of about 10 ms, count 400 looptimes. ******************************* */ void BlinkAlive() { PORTDbits.RD4 = 0; // Turn off LED if (++ALIVECNT == 400) // Increment counter and return if not 400 { ALIVECNT = 0; // Reset ALIVECNT PORTDbits.RD4 = 1; // Turn on LED for one looptime } } /******************************* * Time * * After pushbutton is first pushed, display seconds. ******************************* */
Section 7.7
103
void Time() { if (PBFLAG) { if (++TIMECNT == 100) { TIMECNT = 0; if (++UNITS > '9') { UNITS = '0'; if (++TENS > '5') { TENS = '0'; } } LCDSTRING[0] = TENS; LCDSTRING[1] = UNITS; LCDFLAG = 1; } } }
/******************************* * Pushbutton * * After pushbutton is first pressed, display pushbutton count. ******************************* */ void Pushbutton() { PORTEbits.RE0 = 1; // Power up the pushbutton Nop(); // Delay one microsecond before checking it NEWPB = !PORTDbits.RD7; // Set flag if pushbutton is pressed PORTEbits.RE0 = 0; // Power down the pushbutton if (!OLDPB && NEWPB) // Look for last time = 0, now = 1 { if (!PBFLAG) // Take action for very first PB press { PBFLAG = 1; ALIVECNT = 399; // Synchronize LED blinking to counting TIMECNT = 99; // Update display immediately } else // Take action for subsequent PB presses { if (++PBUNITS > '9') // and increment count of PB presses { PBUNITS = '0'; if (++PBTENS > '9') { PBTENS = '0'; }
104
Chapter 7
// Update display string for simulated LCD // Set flag to display // Save present pushbutton state
/******************************* * UpdateLCD * * This function updates the 8-character LCD if Time * or Pushbutton has set LCDFLAG. ******************************* */ void UpdateLCD() { if(PBFLAG && LCDFLAG) { Display(); LCDFLAG = 0; } } /******************************* * Display * * This function sends LCDSTRING to the LCD. ******************************* */ void Display() { PORTDbits.RD5 = 0; // Wake up LCD display for (i = 0; i < 9; i++) { PIR1bits.SSPIF = 0; // Clear SPI flag SSPBUF = LCDSTRING[i]; // Send byte while (!PIR1bits.SSPIF); // Wait for transmission to complete } PORTDbits.RD5 = 1; // Return RB5 high, ready for next string } /******************************* * LoopTime * * This function puts the chip to sleep upon entry. * For a Timer3 interrupt, it executes the HPISR and then returns to sleep. * For a Timer1 interrupt, it executes the LPISR and then exits. ******************************* */
Section 7.7
105
// Sleep upon entry and upon exit from HPISR // Return only if LPISR has been executed, // which sets LPISRFLAG
FIGURE 7-6 (continued) c) Describe the program flow when the chip is awakened by a Timer1 interrupt but a Timer3 interrupt intervenes just before the LPISR sets the LPISRFLAG. 7-2 Timer1 interrupt interval When the low-priority interrupt service routine cuts out all but 328 counts in its count sequence, it rolls over approximately every 10 ms. As pointed out in Section 7.5, the exact time is 10,009.765 s. a) How many parts per million is this off from the nominal 10 ms? b) How does this compare with the 50-ppm accuracy of the crystal oscillator? c) Repeat parts (a) and (b) if the number of counts of the Timer1 oscillator between Timer1 rollovers were reduced to 327. 7-3 Effect of stopping-starting Timer1 Consider that the output of the Timer1 oscillator is a squarewave, and that the Timer1 counter is clocked on the rising edge of this squarewave. With an oscillator period of about 30.5 s, the oscillator output will be high for about 15 s after the rising edge that produced the low-priority interrupt. During that time, the CPU wakes up and switches on its FCPU = 1 MHz clock. It takes several microseconds to set aside the program counter and a few other CPU registers before clearing the TMR1ON bit to block clock edges from reaching the Timer1 counter. It takes 2 s to update TMR1L before setting the TMR1ON bit again. As shown in Figure 7-4, both the TMR1ON bit and the Timer1 oscillators squarewave output are inputs to an AND gate whose output clocks the Timer1 counter. a) Draw a timing diagram showing the inputs and output of the AND gate assuming the clearing and setting of TMR1ON takes place during the first 15 s after the Timer1 rollover that caused the interrupt. b) Now repeat this, assuming a high-priority interrupt intervenes and delays the entry into the LPISR from the time of the Timer1 rollover by 30.5 n + 15 s, where n is 0 or 1 or 2. c) If the LPISR adds 65,536 + 1 328 = 65,211 to whatever number is in the Timer1 counter, how many Timer1 oscillator periods will occur between the last Timer1 rollover and the next one? Answer this for both parts (a) and (b).
106
Chapter 7
7-4 Reinitializing Timer1 versus adding into it The LoPriISR of Figure 7-6, the T3.c program, adds 0xFEB9 to Timer1. A slightly simpler procedure would be to load 0xFEB9 into Timer1. In both cases, assume TMR3ON = 0 when the low byte, TMR1L, of Timer1 is updated. a) If this adding or loading takes place within 15 s of when Timer1 rolled over, what will be the time until the next rollover in each case? b) Now assume that the adding or loading takes place 31.5 2 + 5 = 68 s after the Timer1 rollover, delayed by an exceptionally long intervening highpriority interrupt service routine. What will be the time until the next rollover in each case? 7-5 Effect of 8-bit add Instead of adding 0xFEB9 to TMR1H:TMR1L, the LoPriISR of the T3.c template program simply adds 0xB9 to TMR1L and loads 0xFE into TMR1H. The possible clocking of TMR1L is prevented by making TMRON = 0 during the addition on the chance that the CPU and the Timer1 oscillator might both try to change TMR1L at the same time. No such issue arises for TMR1H, which will contain 0x00 after the rollover and until 0x100 0xB9 counts of the Timer1 oscillators clock periods have occurred. a) How long is this? b) Using the simplified update scheme for TMR1H:TMR1L of the LoPriISR at any time short of this will produce what interval between the last interrupt and the next one? Explain. 7-6 Worst-case overlapping of interrupts The machine code generated by the line
TMR1L += 0xB9
consists of a 1-s-long instruction to load 0xB9 into a CPU register followed by a second 1-s-long instruction to add the CPU register to TMR1L. An interrupt cannot break into the middle of the execution of an instruction. Hence, were the HPISR to interrupt during the execution of the LPISRs execution of the add instruction, a) What would be the effect on the addition? b) What would be the effect on the counting of Timer1 oscillator clock edges if the HPISR that causes the CPU to digress from the LPISR at this precise point in its execution takes 20 s to execute? c) What is the percent chance of a randomly occurring high-priority interrupt producing the effect of (b), given that the LPISR is executed every 10 ms = 10,000 s?
Chapter
108
Chapter 8
FIGURE 8-1 Stepper motor addition to the Qwik&Low board The stator of the stepper motor is shown in Figure 8-2b. It consists of eight windings on eight poles, with every other winding connected together but wound in the opposite direction. Thus, if the windings are numbered in order, 1 2 3 4 5 6 7 8, then a current into 1 3 5 7 that makes winding number 1 a north pole will make 3 a south pole, 5 a north pole, and 7 a south pole. This is illustrated in Figure 8-3a. Backtracking to the photo of Figure 8-2b, note that each pole is broken into six teeth. These teeth have the same pitch as the 50 teeth of a set of rotor laminations. Furthermore, the 6 teeth on one pole are offset by 1.25 teeth from the 6 teeth on an adjacent pole. It is this 1.25-tooth offset that produces the offset shown in Figure 8-3a, where stator poles 1 and 5 and stator poles 3 and 7 are shown aligned with the rotor while stator poles 2 and 4 and stator poles 6 and 8 are shown offset by half a tooth. A full step is illustrated by the change between the winding energization of Figure 8-3a and that of Figure 8-3b. For this step, the winding current in 1 3 5 7 is turned off while the winding current in 2 4 6 8 is turned on. In response, the rotor rotates one full step. Figures 8-3b, c, d, and e illustrate the sequence of four steps that return to the winding excitation of Figure 8-3a. Fifty of these four-step sequences will result in the stepper motor turning one revolution. This accounts for the motor being designated a 200 steps per revolution motor. A 200 steps per revolution motor can be made to step 400 steps per revolution by means of half-step sequencing of the winding currents. Let A represent a current into the 1 3 5 7 windings while A represents the reverse current into the same windings. Let B and B do the same for current into the 2 4 6 8 windings. Full stepping consists of sequencing the windings as follows:
A B A B A B A B A
Section 8.2
Stepper-Motor Operation
109
(a) Rotor
110
Chapter 8
Stator poles 1 and 5 Stator poles 2 and 6 Stator poles 3 and 7 Stator poles 4 and 8 Rotor S
N S S
N S
(a) Step position 0 Stator poles 1 and 5 Stator poles 2 and 6 Stator poles 3 and 7 Stator poles 4 and 8 Rotor
N S
N S S N
N S S N
(b) Step position 1 Stator poles 1 and 5 Stator poles 2 and 6 Stator poles 3 and 7 Stator poles 4 and 8 Rotor
S N N
S N
(c) Step position 2 Stator poles 1 and 5 Stator poles 2 and 6 Stator poles 3 and 7 Stator poles 4 and 8 Rotor
S N S N S
S N N S
S N N S
(d) Step position 3 Stator poles 1 and 5 Stator poles 2 and 6 Stator poles 3 and 7 Stator poles 4 and 8 Rotor N
N S S
N S
Section 8.3
111
where the winding excitation repeats every four steps. Half stepping is produced by the sequence:
A AB B AB A AB B AB A
Here, the winding excitation does not repeat before eight half steps have been taken. An alternate, popular mode of full stepping makes use of the sequence
AB AB AB AB AB
The sophisticated Allegro Microsystems A3967 stepper-motor driver chip used in Figure 8-1 has two logic inputs that allow a user to select any of four stepping modes: full stepping, half stepping, quarter stepping, and eighth stepping. It does so by controlling not only the direction of the A and B currents but also their magnitude. When half stepping is selected, a step position with only one winding energized uses 100.0% of the current established by the current-sensing resistor while a step position with two windings energized uses 70.7% of that current for each winding. When full stepping is selected, each winding is energized with the 70.7% of the current. Because the magnetic flux produced is the same under all circumstances for all four stepping modes, the torque produced at each step is the same. The effect is to help reduce vibration and noise.
112
Chapter 8
A VMOTOR A or VMOTOR (a) The problem VMOTOR A (split winding) (b) The unipolar stepper motor solution VMOTOR A
A A
Current-sensing resistor
FIGURE 8-4 Two solutions to reversing the current in a stepper motor winding
winding impedes the change. A unipolar driver typically allows the current in a turned-off winding to decay through a diode-resistor circuit. A tradeoff is made between speed of decay and maximum breakdown voltage of the opened transistor switch that confronts the back emf of the collapsing magnetic field in the winding. In contrast, a bipolar driver chip such as Allegros A3967 deals with current control and the breakdown voltage limits of its output transistors as an integrated whole. With all four leads of the bipolar stepper motor connected only to the A3967, the controller chip has full control of the winding voltages and currents.
Section 8.4
Stepper-Motor Driver
113
The $5 regulated 12-V DC wall transformer shown in Figure 8-1 will supply a maximum current of 250 mA per winding. This supply can be replaced by a supply of up to 20 V @ 1.5 A (the 1.5-A limit is imposed by the A3967; the 20-V limit is imposed by the MC33269 voltage regulator). The provided supply is fine for the NEMA Size 17 ( 1.7 sq. in.) stepper motor shown in Figure 8-1. The circuit shown in Figure 8-5b derives a 3.3-V supply from the 12-V input. This supplies the 50-mA current draw of the logic circuitry of the A3967. A 3.3-V regulator ensures that the logic inputs from the Qwik&Low board will never exceed this supply voltage and yet will exceed the 0.7 3.3 V = 2.3 V minimum logic 1 voltage. The resulting current draw on the Qwik&Low coin cell is essentially zero when the two inputs, Step and Dir, are low. When the inputs are both high, the current draw by the two 51.1-k pull-down resistors is 120 A. Consequently, the outputs from the Qwik&Low board that drive these two inputs should be kept low except for the few microseconds needed to control them for each step. The 1.5- resistors shown in Figure 8-5b are used to set the 100% current level in each of the stepper motors two windings; that is, the current level set when one winding is being driven while the other winding is turned off, as occurs with every other half step described in Section 8.2. This current is specified by Allegro to be 100% current = ____
Vref 8 Rs
(8-1)
114
Chapter 8
Blue Bipolar stepper motor rating: 1. Less than 12 V 2. Current per winding will be 0.25 A with default 1.5 current-sensing resistors shown below R3 Example: Jameco 163395 51.1 k .001 F C2 VMOTOR Current sensing resistor R4 1.5 , 1 w 4 .01 F C3 Red TB1 White Terminal block 1 2 3 4 Yellow
U1
24 23 22 21
5 LOAD
10 STEP DIR
19 18
17 16 15 14 13
10 11
R1 51.1 k
R2 51.1 k
12 MS1
Allegro A3967SLB-T Digi-Key 620-1073 $2.75 VMOTOR VCC GND Toggle switch LED S S R B CON1 D1 10 k 1N5818 TB2 Terminal block ON Semiconductor MC33269 Voltage regulator 3 U2 4 + C8 10 F 1
115 VAC
Section 8.5
Stepping
115
where Vref is the voltage connected to pin 1 of the chip, namely the regulated 3.3-V supply voltage. With the 1.5- (1/4-W) resistor on the board, the 100% current has been set to a quarter of an ampere, in deference to the 0.5-A power supply. The Allegro chip pulse-width-modulates the current to each motor winding, turning on the two diagonal transistor switches of the H-bridge driver when the current is low and opening the upper transistor switch when the current has risen to the 100% level. The RC circuits connected to RC1 and RC2 control the PWM frequency. The driver board includes the provision for replacing the resistor in each RC circuit with a one-turn trimpot, should a user wish to experiment with the PWM frequency. The board also includes the option of installing alternate current-setting resistors (alternate to the 1.5- resistors) plus two jumpers to select one pair or the other. Another optional potentiometer can be connected to the PFD pin to control how fast the current in a winding decays for a half step from 100% current to 70.7% current or from 70.7% to 0% current. A third option is to add jumpers to the MS1 and MS2 inputs that allow the default full-stepping mode to be replaced by half stepping, quarter stepping, or one-eighth stepping. Or the MS1 and MS2 pins can be connected to pins on the 10-pin header connected to the Qwik&Low board to allow this choice to be made by the MCU. These options provide refinements to what is, by default, an excellent and clean application of Allegros superb driver chip. For more information on the stepper-motor driver board, refer to Appendix A4.
8.5 STEPPING
Stepper motors are widely used as open-loop positioning actuators because of being able to reach a given position by counting steps from the present position. Furthermore, the error in step position never accumulates; that is, 563 steps CW followed by 563 steps CCW will return the motor output to the same position from which it started, as long as no steps are lost by stepping too fast. With the 10-pin ribbon cable connected between the Qwik&Low board and the stepper-motor controller board as shown in Figure 8-6a, the two MCU pins, RD0 and RD1, control stepping, as indicated in Figure 8-6b. Thus to take a CW step, the following sequence is executed:
PORTDbits.RD0 = 1; Nop(); PORTDbits.RD1 = 1; PORTDbits.RD0 = 0; PORTDbits.RD1 = 0; //Clockwise //Pause 1s //Take step //Zero the pulldown resistor currents
116
Chapter 8
Qwik&Low
R B WY Stepper Driver RD1 RD0 STEP DIR Ribbon Cable BRS S (a) Connections PORTDbits RD1 = 0 MCU
. PORTDbits.RD0 =
PROBLEMS
8-1 Stepper-motor operation Figure 8-2b. Consider Figure 8-3a and also the photograph of
a) On the photograph, label the poles that are north poles and those that are south poles. b) Now do the same with a different color for the excitation of Figure 8-3b. This illustrates how the stator north and south poles move. Given the 1.25tooth offset between adjacent stator poles, it explains the movement of the rotor by one-quarter of the distance from one of its north poles to an adjacent north pole. 8-2 Quarter stepping To get to the data sheet for Allegro Microsystems A3967 stepper-motor driver chip, Google A3967. Within the data sheet, find its specification for the two winding currents at each quarter-step position.
Section 8.5
Stepping
117
a) What is maximum current in a winding relative to the 100% value of Equation 8-1? b) If used with the 12 V @ 0.5 A regulated supply of Figure 8-5b, what current-sensing resistors should be used to limit the supply current to the 0.5 A of the power supply? 8-3 Current-sensing resistor The stepper-motor driver board of Figure 8-5 uses, by default, the full-stepping mode described at the close of Section 8.2. As pointed out there, each stepper winding is energized with a current that is 70.7% of the 100% current set by the current-sensing resistor via Equation 8-1 of Section 8.4. Consequently, another resistor might be used to control the current that drives the stepper motor harder. a) Given the 12 V @ 0.5 A regulated supply that must supply current for two windings, what resistance value is optimum? b) The driver board has provision for adding a second pair of current-sensing resistors plus two 3-pin headers and two jumpers so that the switch from one pair to the other involves moving two jumpers. The board uses Type 1210, 1/4-W surface-mount parts for these resistors. The sizes supplied by Digi-Key are: , 1.0 , 1.2 , 1.5 , 1.8 , 2.2 , Which resistors are a good choice for the second pair, and what is the resulting 70.7% current that will excite each winding? 8-4 Alternate power supply Along with the 12-V DC @ 0.5 A regulated supply discussed here (Digi-Key T983-P5P), Digi-Key also stocks T986-P5P, an 18 V @ 0.33 A regulated supply with the same 2.1-mm barrel connector that can be used with the stepper-motor driver board. Look in the on-line catalog of surplus parts of Herbach and Radman (www.herbach.com) or of Marlin P. Jones & Assoc. (www.mpja.com) for any bipolar (or four wire) stepper motors that specify a voltage and either current per phase or resistance per phase that can be used with the stepper-motor driver board with the 18-V supply. a) What is the part number and what are its specifications? b) Using Equation 8-1 in the text, what should be the resistance of the current-sensing resistor to produce a winding current of 70.7% of the value set by Equation 8-1? 8-5 Clockwise stepping The code of T3.c steps the stepper-motor CCW. a) Modify it to step CW. b) Check the current draw on the coin cell with and without the steppermotor ribbon cable attached to the Qwik&Low board.
118
Chapter 8
8-6 Variable stepping rate The code of T3.c uses an int variable, STEPCNT, to control the stepping rate. To step every 4 ms (i.e., at a rate of 250 steps/s), Timer3 must roll over every 4,000 0.032768 = 131 Timer3 counts This leads to STEPCNT = 65,536 + 1 131 = 65,406 Modify the Pushbutton function of T3.c so that each press of the pushbutton, in addition to incrementing the displayed pushbutton count, also switches the stepping rate between 250 steps/s and 500 steps/s.
Chapter
ANALOG-TO-DIGITAL CONVERTER
9.1 OVERVIEW
This chapter discusses the features and use of the analog-to-digital converter (ADC) module in the PIC18LF4321. This module greatly extends the reach of the MCU into applications that employ any of the wealth of transducers (of temperature, pressure, acceleration, weight, etc.) that produce a voltage output. The ADC is a versatile module in the MCU with the following features: A resolution of 1 part in 1,024. The inclusion of a multiplexer that allows up to 13 pins to be used as analog inputs. A choice of reference voltages. A conversion time of 24 s.
120
Chapter 9
Analog-to-Digital Converter
PIC18LF4321
PORTB RB4 RB3 RB2 RB1 RB0 ADC Ten-bit converter MUX AN12 AN11 AN10 AN9 AN8 AN7 AN6 AN5 AN4 AN3 AN2 AN1 AN0
Qwik&Low resources
Undedicated digital I/O *# Undedicated digital I/O * Undedicated digital I/O *# Undedicated digital I/O *# Interrupt input from RPG Power for RPG interrupt input RPG direction input Power for pushbutton and RPG direction Undedicated analog/digital pin *# Reference voltage input from RD6 Undedicated *# Analog input from temperature sensor Analog input from potentiometer
Analog input
VREF+
Key
VDD
VREF
* Pin brought out to proto area (H4) # Pin brought out to ribbon cable connector (H6)
PORTA RA0 RA1 RA2 RA3 RA5 PORTE RE0 RE1 RE2
FIGURE 9-1 Analog-to-digital converter pin connections both within the PIC18LF4321 and to Qwik&Low resources
Qwik&Low boards potentiometer is connected to the ADCs AN0 input. Because this input shares a pin with the RA0 bit of PORTA, a choice between these two uses of the pin must be made. Figure 9-2 shows how the choice is made. The 4-bit number loaded into the port configuration control bits of ADCON1 can select any number of the 13 possible analog inputs to actually serve in this capacity, with the remaining inputs serving as digital I/O pins. Unlike some other PIC microcontrollers, the specification of
Section 9.2
121
ADCON1 0 0 0 0 1 1 0 0 PCFG, Port configuration control bits 1: VREF+ = AN3 pin VCFG0 0: V REF+ = VDD 1: VREF = AN2 pin VCFG1 0: V REF = GND Unused AN12 AN11 AN10 AN9 AN8 AN7 AN6 AN5 AN4 AN3 AN2 AN1 D D A A A A A A Number of analog channels 0 1 2 3 4 5 6 7
1 1 0 0 1 1 0 0
1 0 1 0 1 0 1 0
D D D D D D D D
D D D D D D D D
D D D D D D D D
D D D D D D D D
D D D D D D D D
D D D D D D D D
D D D D D D D A
D D D D D D A A
D D D D D A A A
D D D D A A A A
D D D A A A A A
D A A A A A A A
FIGURE 9-2 Selection of I/O pins to be analog inputs the number of analog inputs selected also specifies which pins will be the analog pins. Furthermore, the choice is critical in the following sense: Any pin that is selected to be an analog pin has its digital I/O circuitry disabled. Any pin that is selected to be a digital pin can then be set up to be a digital (high-impedance) input and also used as an analog input. However, for an analog input in the middle voltage range (well above 0 V and well below 3 V), the digital circuitry will exhibit excessive leakage current. The two devices designed into the Qwik&Low board having analog inputs are: The potentiometer. A temperature sensor. Also, the output pin of the MCU that powers the temperature sensor is used as the VREF+/AN3/RA3 pin, taking advantage of a sensor whose voltage output is proportional to its supply voltage as well as its temperature. These have been assigned to
AN0
122
Chapter 9
Analog-to-Digital Converter
inputs AN0, AN1, and AN3 respectively so that a choice of 1011 for the Port Configuration control bits of Figure 9-2 will designate all but one (AN2) of the remaining pins to be available as possible digital I/O pins. Of these digital pins, any that are unused will be set up as outputs. Whether initialized high or low, the output voltage will not subject the also enabled digital input circuitry to a leakage-current-inducing voltage in the middle range. Consider again Figure 9-1. One more analog device can easily be added to the proto area of the Qwik&Low board by connecting the analog devices output to the MCUs AN2 input. Another analog input can be connected to the AN4/RA5 input pin if the port configuration control bits of Figure 9-2 are changed to 1010.
(a) ADC format control bit ADRESL ADRESH ADRES 0 0 0 0 0 0 b b b b b b b b b b for ADCON2bits ADFM = 1
. .
ADRESL ADRESH ADRES b b b b b b b b b b 0 0 0 0 0 0 for ADCON2bits ADFM = 0 (b) Two alternative sixteen-bit result formats ADRES 0 0 0 0 0 0 b b b b b b b b b b (with ADFM = 1) (c) Sixteen-bit result in ADRES ranging from 01023 as input ranges from 0 V to VDD ADRESH b b b b b b b b (with ADFM = 0) (d) Eight-bit result in ADRESH ranging from 0255 as input ranges from 0 V to VDD ADRESL b b b b b b b b (with ADFM = 1) (e) Eight-bit result in ADRESL ranging from 0255 for inputs less than VDD/4 = 750 mV
or
Section 9.5
ADC Timing
123
and ADRESL, as shown in Figure 9-3b. To use the full 01,023 range of the ADC output, ADFM should be set prior to the conversion. On its completion, ADRES can be read as the unsigned int variable of Figure 9-3c. Obtaining the output of the single-turn potentiometer on the Qwik&Low board as a number having 256 values can be achieved by first clearing ADFM to zero, to left justify the result. When the conversion is complete, the upper 8 bits of the conversion will reside in ADRESH, as illustrated in Figure 9-3d. By reading only this unsigned char variable, the least significant 2 bits of the 10-bit conversion are ignored, throwing away the extra resolution provided by those 2 bits. What remains in ADRESH is a value of 0 if the potentiometer is turned fully CCW and a value of 255 if the potentiometer is turned fully CW. Another option arises for an analog device whose output has a range reaching up to less than 750 mV (i.e., less than VDD/4). The choice of Figure 9-3e provides the same resolution as using the 16-bit ADRES output of Figure 9-3b but with the computational advantage, for subsequent scaling of the output, of an 8-bit output.
124
Chapter 9
Analog-to-Digital Converter
is the ADC clock period. is the ADC acquisition time required between when an ADC channel has been selected and when a conversion can be initiated. is the source (i.e., Thevenin) resistance of the device whose voltage output is being measured. is the optional external high reference voltage (if used) (a) ADC definitions 2.5 s > TAD > 1.4 s TACQ > 3.7 s for RSOURCE 5 k > 6.9 s for RSOURCE 20 k VDD + 3.0 V > VREF+ > 1.8 V Conversion time = 12 TAD (b) PIC18LF4321 specifications
ADCON2
0 0 0 1 0 0 1 0 1 0 1 1 0 0 0 0 1 0 0 1 1 1 TAD = 2 s for FOSC = 1 MHz TAD = 2 s for FOSC = 2 MHz TAD = 2 s for FOSC = 4 MHz TAD = 2 s for FOSC = 8 MHz TAD 2.5 s using the ADCs internal RC oscillator TACQ = 2 TAD = 4 s Unused ADFM 1: Right justify result 0: Left justify result See Figure 9.3
Figure 9-4c indicates that the least significant 3 bits of ADCON2 should be initialized to 001, making TAD = 2 s. A value of TAD of 1 s or less is too fast for accurate conversion while a TAD of 4 s or greater will produce conversions that take 48 s or 96 s. With TAD = 2 s, the 10-bit conversion will take 12 TAD = 24 s. The time between selecting which input pin is to be multiplexed into the ADC and when the conversion is begun is automatically controlled by the TACQ parameter. The 001 setting of Figure 9-4c selects an acquisition time of 4 s, sufficient for the two analog devices on the Qwik&Low board. Among the factors entering into the acquisition time determination, the two that a user has some control over are the source impedance of each analog source and the needed resolution of the result.
Section 9.6
125
Example 9-1 Determine the maximum source resistance of the 20-k potentiometer. Solution The source resistance is really the Thevenin resistance of a source. When the potentiometer is turned fully CCW, the source resistance is 0 , the short circuit to ground. When the potentiometer is turned fully CW, the source resistance is again 0 , the short circuit to VDD (which is the same as a short circuit to ground, assuming the coin cell has essentially zero internal resistance). When the potentiometer wiper is at its midpoint, the source resistance is at its maximum of 10 k to VDD in parallel with the 10 k to ground. That is, the maximum source resistance is 5 k. The source resistance affects the maximum acquisition time. A small internal sampling capacitor in the ADC charges up to match the (Thevenin) voltage of the analog source. The RC time constant of this charging depends on internal resistances in the ADC in series with the source resistance.
GO_DONE Unused 0 0 0 0 0 0 0 0 0 1 0 0 1 1 0 0 1 0 1 0 Select Select Select Select Select AN0 AN1 AN2 AN3 AN4
126
Chapter 9
Analog-to-Digital Converter
ADCON1 = 0x0b; // ADCON2 = 0x09; // PORTAbits.RA7 = 1; // ADCON0 = 0x03; // while (ADCON0bits.GO_DONE); PORTAbits.RA7 = 0; // ADCON0bits.ADON = 0; // <read result from ADRESH>
Initialize ADC for Q&L's four analog inputs ADC clock with Fosc = 4 MHz; left justify result Power up potentiometer (see Figure 3-2) Power up ADC; select pot input; start conversion // Wait for completion of conversion Power down potentiometer Power down ADC
FIGURE 9-6 Use of ADC to read potentiometer Because the acquisition time setting in the ADCON2 register inserts a delay after the GO_DONE bit has been set and because the ADC module powers up essentially instantaneously relative to the 8 s acquisition time selected, ADCON0 can be loaded with the one value that will: Power up the ADC. Select an analog input. Initiate the conversion. Example 9-2 Show the code to convert the output of the potentiometer into an 8-bit value in ADRESH. Solution Figure 9-6 shows the code lines to use VDD as the reference voltage, to use the timing associated with FOSC = 4 MHz, and to form ADRESH. Before the conversion is initiated, the output on RA7 that powers the potentiometer must be set, as shown back in Figure 3-2. Then the ADFM bit is cleared, to have the upper 8 bits of the 10-bit result put into ADRESH. The conversion of the potentiometer input to AN0 is then initiated. The result will be found in TACQ + 12 TAD = 4 + (12 2) = 28 s with the automatic clearing of the GO_DONE bit signaling that ADRESH is ready with the result. The remaining lines power down the potentiometer and the ADC module as soon as possible before the result in ADRESH is read and used.
Section 9.8
127
Example 9-3 If the potentiometer output is read every tenth of a second, what is the effect on average current draw of having the conversion take place during sleep versus while the MCU is awake? Solution The CPU will spend less time awake in the main loop if the conversion is put off until the chip sleeps. But between the setting of the GO_DONE bit and its automatic clearing at the completion of the conversion, the awake CPU runs for the 28 s pointed out at the end of the last section. Referring back to Figure 2-4, a current draw of 1.036 mA was measured when the CPU was running with FOSC = 4 MHz but otherwise doing nothing to draw extra current. If the CPU is awake for an extra 28 s every tenth of a second, this represents an extra average current draw by the CPU of 28 _______ 1,036 A = 0.29 A 100,000 regardless of how much current the ADC module draws, since that is essentially the same whether carried out while asleep or awake. On the other hand, if the conversion is carried out while asleep, its quiescent current of 1.0 A while not converting will last for the duration of sleep within one pass through the main loop. This will contribute some fraction of 10ms ______ 1.0 A = 0.10 A 100ms If the CPU spends 2 ms out of every 10 ms loop time doing useful work before going to sleep, the net benefit of doing 10 conversions/s while asleep is about 0.29 0.08 = 0.21 A of average current.
128
Chapter 9
Analog-to-Digital Converter
Although Analog Devices does not specify a settling time for this temperature sensor, clearly this becomes an important consideration in the following sequence: Set RD6 = 1 to power up the sensor. Pause to allow the sensor to settle. Initiate the measurement. Wait for the completion of the conversion. Clear RD6 = 0 to power down the sensor.
The settling time of the sensor has two measurable components: The settling out of its underdamped step response (10 s). Any longer asymptotic tail of this step response. By waiting perhaps 20 s after raising RD6 and then making repeated full 10-bit ADC measurements spaced apart by the 24-s conversion time of the ADC, the fast settling of the 10-bit readings can be verified. With essentially no change between the first and the second reading, evidently a 20-s pause is sufficient. The transfer function of the AD22103 is illustrated in graphical form in Figure 9-7a when the sensors supply voltage, VS, is 3.3 V. More generally, for a sensor supply voltage in the specified range of +2.7 V to +3.6 V, the output voltage, VO, is given by the equation of Figure 9-7b. Analog Devices provides a somewhat ambiguous accuracy specification for the AD22103. The typical error of a room temperature measurement should be within half a degree Centigrade. Over the full range for the part, namely 0C100C, the typical error should be within 0.75C. Often it is useful to obtain temperature measurements with a resolution that is finer than the absolute accuracy that is warranted for a sensor. For temperature changes, the resolution produces temperature-difference measurements that are essentially correct. For example, if the sensor output voltage presently translates to a temperature of 71.4F, whereas 10 min ago it translated to a temperature of 70.6F, then the increase over the last 10 min is actually quite close to 71.4F 70.6F = 0.8F even though the absolute value of 71.4F is probably less accurate. Obtaining a temperature reading from an ADC measurement is expedited by translating the expression of Figure 9-7b into an expression of temperature in terms not of voltage but of ADRES, the 16-bit register that holds the 10-bit ADC output. If the output voltage equals 3,050 mV at 100C when the supply voltage is 3,300 mV, then the ADC converter would translate this to 3,050 _____ 1,024 = 946.424 counts 3,300 where, for the sake of the calculations that follow, the three digits to the right of the decimal point are warranted as an intermediate result, even though the ADC itself would show this result as 946. With the same 3,300 mV supply voltage, 0C would translate to 250 _____ 1,024 = 77.576 counts 3,300
Section 9.8
129
VO 3050 mV
250 mV Centigrade 100C (a) Transfer function for VS (the supply voltage) = 3.3 V = 3300 mV VO = VS (volts) 250 mV + (28 mV/C) Centigrade millivolts 3.3 (volts) (b) General transfer finction ADRES = 946.424 77.576 Centigrade + 77.575 100 0 = (8.68848 Centigrade) + 77.575 (c) Translation of (b) to counts of ADRES Centigrade = ADRES 8.9285 8.68848 C
(d) Reexpressing centigrade temperature as a function of ADRES Fahrenheit = 9 Centigrade + 32 = ADRES + 15.9287 5 4.82693 (e) Expressing Fahrenheit temperature as a function of ADRES
Because of the ratiometric feature of the sensor, these are the same readings that would be obtained with the measurements using the Qwik&Low circuit and its lower VDD and its even slightly lower RD6 supply voltage to both the temperature sensor and the VREF+ pin used by the ADC. The equation of Figure 9-7c expresses the resulting value of ADRES as a function of the Centigrade temperature, again carrying extra (intermediate) digits of resolution.
130
Chapter 9
Analog-to-Digital Converter
This is inverted in Figure 9-7d to express the Centigrade temperature as a function of the value of ADRES. The conversion to Fahrenheit is shown in Figure 9-7e. Carrying out the calculation of Figure 9-7d using integer arithmetic (for speed) requires that numerator and denominator be multiplied by the same integer constant before the division takes place. Recall from Figure 9-3b that clearing the ADFM bit of the ADCON2 register before the ADC conversion takes place will produce a leftjustified 10-bit result. In effect, this multiplies ADRES by 64. If the denominator of the quotient in Figure 9-7d is also multiplied by 64, the resulting expression for the Centigrade temperature becomes that shown in Figure 9-8a. If the terms in this equation are multiplied by 10, the integer result will be expressed in tenths of a degree Centigrade, as shown in Figure 9-8b. Note that a resulting integer of 253 represents a temperature of 25.3C. If the Centigrade to Fahrenheit conversion of 9 F = __ C + 32 5 is applied to these equations, the equations of Figure 9-8c and d result. A calculation can be massaged to shorten its execution time. Note that multiply operations are significantly faster than divide operations. Also, a multiply or a divide of a multiple-byte number by 28 = 256 = 0x100 amounts to moving the bytes left or right by 1 byte. For example, 28 0x00001234 = 0x00123400 Applying these ideas to the equation of Figure 9-8d to obtain the temperature in tenths of a degree Fahrenheit, the equation is reexpressed in Figure 9-9a. Instead of FIGURE 9-8 Temperature equations after converting voltage with ADCON2bits.ADFM = 0 (to left justify the output and, in effect, multiply ADRES by 64). Centigrade = (ADRES / 556.063) 8.9285 (a) For Centigrade measurement with 1C resolution TenthC = (ADRES / 55.6063) 89.285 (b) For Centigrade measurement with 0.1C resolution Fahrenheit = (ADRES / 308.924) + 15.9287 (c) For Fahrenheit measurement with 1F resolution TenthF = (ADRES / 30.8924) + 159.287 (d) For Fahrenheit measurement with 0.1F resolution
Section 9.8
131
dividing by the denominator of 30.8924, both the numerator and denominator can be first multiplied by 216 _______ = 2,121.43 30.8924 so that the denominator becomes 216 and also the division by 216 becomes two 1-byte shifts of the numerator. The resulting equation is shown in Figure 9-9c. To avoid overflow when the int register ADRES is multiplied by the int constant 2,121, ADRES can first be copied into the 4-byte-long variable, VALUE. The computation of Figure 9-9c is shown broken down into six steps for the benefit of optimization by the C18 compiler. The calculation in this form takes just 111 CPU clock periods.
FIGURE 9-9 Speeding up the calculation of temperature with 0.1F resolution TenthF = (ADRES / 30.8924) + 159.287 (a) For Fahrenheit measurement with 0.1F resolution 2^16 / 30.8924 = 65536 / 30.8924 = 2121.43 (b) Forming a multiplier for numerator and denominator that will make the denominator equal to 2^16 TenthF = ((2121 * ADRES ) >> 16 ) + 159 (c) Resulting equation unsigned long VALUE; unsigned int BIGNUM; (d) Definition of global variables VALUE = ADRES; VALUE *= 2121; VALUE >>= 8; VALUE >>= 8; VALUE += 159; BIGNUM = VALUE; // 32 bits to avoid overflow // 32-bit repesentation // 16-bit representation
// 16-bit representation
(e) Calculation, broken down into separate steps that the C18 compiler handles better than a single-line calculation. Execution takes 111 s.
132
Chapter 9
Analog-to-Digital Converter
PROBLEMS
9-1 Running average Write a Filter function that maintains an array of eight int values: RESULT[0], RESULT[1], . . . , RESULT[7] a) In the Initial function, read a 10-bit value using the potentiometer output (as an easily available vehicle for averaging). Load this value into all eight RESULT[i] variables and form SUM equal to one of these, shifted left three places to multiply it by eight. b) Every tenth of a second, subtract RESULT[7] from SUM, copy each RESULT value to the next location using a for loop, collect a new sample from the potentiometer, add it to SUM, and copy the sample value into RESULT[0]. Finally, shift SUM right three places into the int variable, BIGNUM (to divide it by eight, the number of samples in the running average) and then use the ASCII4 function from Figure 6-8b to break out the four digits and display them on the LCD. 9-2 ADC timing a) Modify the Temperature function in the TenthsFahr.c file on the www. qwikandlow.com web site so that after collecting a sample, but before powering down the sensor, immediately collect a second sample. Display these two samples side by side on the LCD using the format exemplified by 73.2 73.4 Update the values every half second. b) Does the extra delay produce a difference between the two values? If so, collect three samples (for timing), throw away the first and display the second and third. Does the extra delay help? c) Set RB0 before the initial setting of RD6 that powers up the temperature sensor. Clear it before the first loading of ADCON0 that produces a settled value of temperature. What is the resulting sensor setup time? 9-3 TenthsCent.c file a) Modify the TenthsFahr.c file to use the equation of Figure 9-8b and a scheme analogous to that of Figure 9-9 to calculate the temperature with 0.1C resolution. b) Set and clear RB0 around the calculation of Part (a), analogous to Figure 9-9e, and measure its execution time. c) Compare the temperature you get with that of a mercury (or liquid) thermometer.
Chapter
10
134
Chapter 10
FIGURE 10-1 Two rotary pulse generators (Bourns 3315-C-001006L; ALPS EC11G1524402) The pull-up resistors are powered up by raising RB3 when the RPG output is to be queried and powered down immediately afterwards, thus once again reducing the average current draw to a negligible amount. The inputs to RB1 and RB0 traverse through six cycles per revolution. Each cycle produces four output states, giving 24 states per revolution, as shown in Figure 10-2b. The ALPS RPG, also pictured in Figure 10-1 and actually used on the Qwik&Low board, has a resolution of 30 detented increments per revolution. Detented means that the unit includes a mechanism that causes the RPG shaft to click from one of its 30 increments to the next, and that holds the position when released. The 24 or 30 increments per revolution resolution of these two RPGs is typical of the resolution found in many commercial products and instruments. Although it might seem that finer is better, a user will find it frustrating to have an undetented resolution so fine that one or more increments or decrements can occur when the knob is released. In the sections that follow, the use of the Bourns RPG will be described first. It provides a good example of polled operation. It also provides insight that will help clarify why an interrupt handler is used with the detented ALPS unit.
Section 10.3
RPG Functionality
135
22.6 k
Conductive pattern Non-conductive pattern (a) Brush/wheel configuration One revolution (6 cycles) (24 states)
(b) Output
FIGURE 10-2 RPGs encoding wheel and brushes The Bourns unit has a specified maximum turning rate of 120 rpm, or 2 revolutions/s. At this maximum rate, the minimum time between increments is about 20 ms. By reading the RPG every time around the main loop (i.e., every 10 ms), every change of state will be detected. Furthermore, because the application code integrates the effect of many rapidly occurring increments or decrements when the RPG is turned fast, missing any counts because of too-fast turning goes unnoticed. The Bourns RPG carries a
136
Chapter 10
maximum contact-bounce time specification of 5 ms. Consequently, reading its output every 10 ms effectively debounces it. The ALPS unit with its detented output is used somewhat differently. Its two output pins traverse the same 2-bit Gray code sequence 00 01 11 10 00 when turned in one direction and 00 10 11 01 00 when turned in the other direction. However, only the 00 and 11 states represent detented positions.
Other functions called within the main loop can use the value of DELRPG to increment or decrement a parameter value in response to a change in the RPG position. When reading external pins from within a function, care should be taken to read the pins just once. In the case of the RPG function, the state of PORTB is read and masked, to form NEWRPG. It is needed at the beginning of the function to compare with the state 10 ms ago. At the conclusion of the function, the present state of NEWRPG is used to update OLDRPG, saving this value for use 10 ms later, next time around the main loop. For bug-free code, OLDRPG should be updated with the value of NEWRPG read at the beginning of the function rather than by reading and masking PORTB again. The RPG function compares the value of NEWRPG (0, 1, 2, or 3) with the value found 10 ms ago. If these values differ, a change has taken place in the RPG position. If the bits have changed in a CW direction, one of the following four cases will have occurred: 00 01 01 11 11 10 10 00
Section 10.4
137
// // // //
Power up RPG pullup resistors Wait a microsecond Load OLDRPG for RPG Power down RPG
/******************************* * RPG * * This function generates DELRPG = +1 for a CW increment of the RPG, * -1 for a CCW increment, and 0 for no change. ******************************* */ void RPG() { DELRPG = 0; // Clear for "no change" return value PORTBbits.RB3 = 1; // Power up RPG pullup resistors Nop(); // Wait a microsecond NEWRPG = PORTB & 0b00000011; // Read PORTB PORTBbits.RB3 = 0; // Power down RPG if (NEWRPG != OLDRPG) // Any change? { if (0b00000010 & (NEWRPG ^ (OLDRPG << 1))) // CW or CCW? { DELRPG = -1; // CCW change } else { DELRPG = +1; // CW change } } OLDRPG = NEWRPG; // Save present RPG state for next loop } (b) The function.
FIGURE 10-3 RPG function Note that in every case the MSb (most-significant bit) of the new number is the same as the LSb of the old number. If the RPG is changed in a CCW direction, then: 00 10 10 11 11 01 01 00
138
Chapter 10
In every case the MSb of the new number is the complement of the LSb of the old number. This information forms the basis for the test, namely: Shift OLDRPG left so that its bit 0 is moved to the bit 1 position. Exclusive-OR the resulting shifted value in OLDRPG with NEWRPG. Mask off all but bit 1 to obtain a nonzero value for a CCW change and a zero value for a CW change. The value of DELRPG is set accordingly. The RPGcounter function of Figure 10-4 uses the RPG output, DELRPG, to increment or decrement RPGNUM and to display it on the two rightmost characters of the LCD.
/******************************* * RPGcounter * * This function uses DELRPG to increment/decrement the signed char variable, * RPGNUM. RPGNUM is then displayed in LCDSTRING[6] and LCDSTRING[7]. ******************************* */ void RPGcounter() { RPGNUM += DELRPG; // Update counter if RPG has been turned if (RPGNUM > 99) { RPGNUM = 0; } else if (RPGNUM < 0) { RPGNUM = 99; } LCDSTRING[6] = '0' + (RPGNUM / 10); // Tens digit LCDSTRING[7] = '0' + (RPGNUM % 10); // Units digit LCDFLAG = 1; // Set flag to display } (b) The function.
Section 10.5
139
The output representing a change occurs only at each detented position when the two pins are either in state 00 or state 11. But the transition state of 01 or 10 must also be read, to determine the direction of change. Because the detent mechanism inserts a snap into the movement, the two pins must use a shorter interval between samples (e.g., 3.333 ms) to read each state reliably during the transition from one detented position to the next. The interrupt approach of Figure 3-2, repeated in Figure 10-5a, resolves these two issues. For this approach, the pull-up resistor on the output that goes to the MCUs interrupt pin must be continuously pulled high, resulting in a continuous current draw when the RPG is left standing in state 00. Using a 1-M pull-up resistor has two beneficial effects: For an application that uses the RPG, the RE2 pin that powers the pull-up resistor for the interrupt input produces a quiescent current draw of only 3 A when the RPG outputs are at the 00 position (and 0 A otherwise). The 1-M pull-up resistor on the INT2 interrupt input coupled with the associated low stray capacitance filters out the effect of contact bounce. To determine the direction of rotation of the RPG, use is made of an INTEDG2 control bit that determines whether the INT2 input will be sensitive to a rising or a falling edge: If INTEDG2 = 1, then an interrupt will occur in response to a rising edge into INT2. If INTEDG2 = 0, then an interrupt will occur in response to a falling edge into INT2. In response to each interrupt, INTEDG2 is toggled. Consequently, every edge into INT2 will produce an interrupt. Consider the CCW rotation shown in Figure 10-5b. Note that in going from one detented position to the next, either a rising edge or a falling edge always occurs on the INT2 input. Furthermore, for this CCW direction of motion, the state of INTEDG2 at the time of the INT2 interrupt always matches the state of the RE1 input read from the other RPG output. On the other hand, CW rotation produces a mismatch between INTEDG2 and RE1, as shown in Figure 10-5c. This match or mismatch condition is used by the high-priority interrupt service routine of Figure 10-6b. Because more than one increment or decrement of DELRPG may take place during a 10-ms looptime, the HPISR may increment or decrement DELRPG more than once in that interval. The RPGcounter function of Figure 10-6c decrements a positive value of DELRPG toward zero while at the same time incrementing RPGNUM. It increments a negative value of DELRPG toward zero while at the same time decrementing RPGNUM. Thus RPGNUM simply accumulates all the increments and decrements of DELRPG into a value that is converted to a two-digit ASCII representation. The LCD is updated by setting LCDFLAG only when the RPG is turned. A complete, testable source file, RPG.c, is listed in Figure 10-7. It includes the modification of the Initial function to handle the RPG shown in Figure 10-6a. Note
140
Chapter 10
(a) RPG connections CCW direction of motion INT2 INTEDG2 Value = RE1 Value = RE1 Detented positions (b) CCW motion for which INTEDG2 = = RE1 at time of interrupt CW direction of motion INT2 INTEDG2 Value = RE1 Value = RE1 Detented positions (c) CW motion for which INTEDG2 != RE1 at time of interrupt 0 1 1 0 0 1 1 0 1 1 0 0 1 1 0 0
FIGURE 10-5 Determining the direction of rotation at the time of each interrupt that after RE2 of PORTE is set to power the 1-M pull-up resistor for the interrupt input from the RPG, a 100-s delay is inserted before RB2/INT2 is read and used to initialize INTEDG2 appropriately. Unlike other inputs that can be read with only a microsecond pause after the associated pull-up resistor has been powered up, the
Section 10.5
141
PORTEbits.RE2 = 1; // Power up the pullup for the RPG's interrupt Delay(10); // Pause for 100 us INTCON2bits.INTEDG2 = !PORTBbits.RB2; // Select initial interrupt edge INTCON3bits.INT2IE = 1; // Enable INT2 interrupt source INTCON3bits.INT2IP = 1; // Use INT2 for high-priority interrrupts INTCON3bits.INT2IF = 0; // Clear interrupt flag DELRPG = 0; // Indicate no initial edge RPGNUM = 0; // and initial RPG position of 00 LCDFLAG = 1; // Display initial 00 value (a) Initial function additions
/******************************* * HiPriISR * * Respond to rising and falling edges on INT2 input from RPG. ******************************* */ void HiPriISR() { PORTEbits.RE0 = 1; // Power pullup resistor to read direction INTCON3bits.INT2IF = 0; // Reset interrupt flag if (PORTEbits.RE1 == INTCON2bits.INTEDG2) // Direction? { ++DELRPG; } else { --DELRPG; } PORTEbits.RE0 = 0; // Power down pullup resistor INTCON2bits.INTEDG2 ^= 1; // Toggle edge sensitivity } (b) High-priority interrupt service routine
/******************************* * RPGcounter * * This function uses DELRPG to update the signed char variable, RPGNUM. * DELRPG is returned to zero. * LCDSTRING[6] and LCDSTRING[7] display RPGNUM. ******************************* */ void RPGcounter() { while (DELRPG > 0) { --DELRPG;
142
Chapter 10
if (++RPGNUM > 99) // Increment RPGNUM, modulo 100 { RPGNUM -= 100; } TEMPCHAR = RPGNUM; LCDSTRING[6] = '0' + TEMPCHAR / 10; LCDSTRING[7] = '0' + TEMPCHAR % 10; LCDFLAG = 1; // Set flag to display } while (DELRPG < 0) { ++DELRPG; if (--RPGNUM < 0) // Decrement RPGNUM, modulo 100 { RPGNUM += 100; } TEMPCHAR = RPGNUM; LCDSTRING[6] = '0' + TEMPCHAR / 10; LCDSTRING[7] = '0' + TEMPCHAR % 10; LCDFLAG = 1; // Set flag to display } } (c) RPGcounter function called from main.
Section 10.5
143
* LoopTime * * HiPriISR * * LoPriISR * ******************************* */ #include <p18f4321.h> // Define PIC18LF4321 registers and bits
/******************************* * Configuration selections ******************************* */ #pragma config OSC = INTIO1 // #pragma config PWRT = ON // #pragma config LVP = OFF // #pragma config WDT = OFF // #pragma config MCLRE = ON // #pragma config PBADEN = DIG // #pragma config CCP2MX = RB3 // #pragma config BOR = SOFT // #pragma config BORV = 3 // #pragma config LPT1OSC = OFF // /******************************* * Global variables ******************************* */ char LCDFLAG; // char LPISRFLAG; // unsigned char i; // unsigned int DELAY; // unsigned int ALIVECNT; // signed char DELRPG; // signed char RPGNUM; // signed char TEMPCHAR; // /******************************* * Variable strings ******************************* */ char LCDSTRING[] = "
Use internal osc, RA6=Fosc/4, RA7=I/O Enable power-up delay Disable low-voltage programming Disable watchdog timer initially Enable master clear pin PORTB<4:0> = digital Connect CCP2 internally to RB3 pin Brown-out reset controlled by software Brown-out voltage set for 2.0V, nominal Deselect low-power Timer1 oscillator
Flag, set to send string to display Flag, set when LP interrupt has been handled Index into strings Sixteen-bit counter for obtaining a delay Scale-of-400 counter for blinking "Alive" LED RPG output For display of RPG position Temporary signed character
144
Chapter 10
/******************************* * Macros ******************************* */ #define Delay(x) DELAY = x; while(--DELAY){ Nop(); Nop(); } /******************************* * Interrupt vectors ******************************* */ // For high priority interrupts: #pragma code high_vector=0x08 void interrupt_at_high_vector(void) { _asm GOTO HiPriISR _endasm } #pragma code #pragma interrupt HiPriISR // For low priority interrupts: #pragma code low_vector=0x18 void interrupt_at_low_vector(void) { _asm GOTO LoPriISR _endasm } #pragma code #pragma interruptlow LoPriISR /******************************* * HiPriISR * * Respond to rising and falling edges on INT2 input from RPG. ******************************* */ void HiPriISR() { PORTEbits.RE0 = 1; // Power pullup resistor to read direction INTCON3bits.INT2IF = 0; // Reset interrupt flag if (PORTEbits.RE1 == INTCON2bits.INTEDG2) // Direction? { ++DELRPG; }
Section 10.5
145
/******************************* * LoPriISR * * Control 10 ms looptime ******************************* */ void LoPriISR() { T1CONbits.TMR1ON = 0; // TMR1L += 0xB9; // T1CONbits.TMR1ON = 1; // TMR1H = 0xFE; // PIR1bits.TMR1IF = 0; // LPISRFLAG = 1; // }
Pause Timer1 counter Cut out all but 328 counts of Timer1 Resume Timer1 counter Upper byte of Timer1 will be 0xFE Clear interrupt flag Set a flag for LoopTime
/////// Main program ////////////////////////////////////////////////////////// /******************************* * main ******************************* */ void main() { Initial(); // while (1) { PORTCbits.RC2 ^= 1; // BlinkAlive(); // RPGcounter(); UpdateLCD(); // LoopTime(); // } }
Initialize everything
Toggle pin, for measuring loop time Blink "Alive" LED Update LCD Use Timer1 to wakeup and loop again
/******************************* * Initial * * This function performs all initializations of variables and registers. ******************************* */
146
Chapter 10
void Initial() { OSCCON = 0b01100010; SSPSTAT = 0b00000000; SSPCON1 = 0b00110000; ADCON1 = 0b00001011; TRISA = 0b00001111; TRISB = 0b01000100; TRISC = 0b10000000; TRISD = 0b10000000; TRISE = 0b00000010; PORTA = 0; PORTB = 0; PORTC = 0; PORTD = 0b00100000; PORTE = 0; LCDFLAG = 0; ALIVECNT = 300; SSPBUF = ' '; Delay(50000); RCONbits.SBOREN = 0; T1CON = 0b01001111; TMR1H = 0xFE; TMR1L = 0xB9; PIE1bits.TMR1IE = 1; IPR1bits.TMR1IP = 0; LPISRFLAG = 0;
// Use Fosc = 4 MHz (Fcpu = 1 MHz) // Set up SPI for output to LCD // // // // // // // RA0,RA1,RA2,RA3 pins analog; others digital Set I/O for PORTA Set I/O for PORTB Set I/O for PORTC Set I/O for PORTD Set I/O for PORTE Set initial state for all outputs low
// except RD5 that drives LCD interrupt // // // // // // // // // // // Flag to signal LCD update is initially off Blink immediately Send a blank to initialize state of SPI Pause for half a second Now disable brown-out reset Timer1 - loop time via low-pri interrupts Set Timer1 to be 10 ms away from next roll over (65536 + 1 - 328 = 0xFEB9) Enable local interrupt source Use Timer1 for low-priority interrupts Flag to signal that LPISR has been executed
PORTEbits.RE2 = 1; // Power up the pullup for the RPG's interrupt Delay(10); // Pause for 100 us INTCON2bits.INTEDG2 = !PORTBbits.RB2; // Select initial interrupt edge INTCON3bits.INT2IE = 1; // Enable INT2 interrupt source INTCON3bits.INT2IP = 1; // Use INT2 for high-priority interrrupts INTCON3bits.INT2IF = 0; // Clear interrupt flag DELRPG = 0; // Indicate no initial edge RPGNUM = 0; // and initial RPG position of 00 LCDFLAG = 1; // Display initial 00 value RCONbits.IPEN = 1; INTCONbits.GIEL = 1; INTCONbits.GIEH = 1; } /******************************* * BlinkAlive * * This function briefly blinks the LED every four seconds. * With a looptime of about 10 ms, count 400 looptimes. ******************************* */ // Enable high/low priority interrupt feature // Global low-priority interrupt enable // Enable both high and low interrupts
Section 10.5
147
// Turn off LED // Increment counter and return if not 400 // Reset ALIVECNT // Turn on LED for one looptime
/******************************* * RPGcounter * * This function uses DELRPG to update the signed char variable, RPGNUM. * DELRPG is returned to zero. * LCDSTRING[6] and LCDSTRING[7] display RPGNUM. ******************************* */ void RPGcounter() { while (DELRPG > 0) { --DELRPG; if (++RPGNUM > 99) // Increment RPGNUM, modulo 100 { RPGNUM -= 100; } TEMPCHAR = RPGNUM; LCDSTRING[6] = '0' + TEMPCHAR / 10; LCDSTRING[7] = '0' + TEMPCHAR % 10; LCDFLAG = 1; // Set flag to display } while (DELRPG < 0) { ++DELRPG; if (--RPGNUM < 0) // Decrement RPGNUM, modulo 100 { RPGNUM += 100; } TEMPCHAR = RPGNUM; LCDSTRING[6] = '0' + TEMPCHAR / 10; LCDSTRING[7] = '0' + TEMPCHAR % 10; LCDFLAG = 1; // Set flag to display } } /******************************* * UpdateLCD * * This function updates the 8-character LCD if the LCDFLAG is set. ******************************* */
148
Chapter 10
void UpdateLCD() { if (LCDFLAG) { Display(); LCDFLAG = 0; } } /******************************* * Display * * This function sends LCDSTRING to the LCD. ******************************* */ void Display() { PORTDbits.RD5 = 0; // Wake up LCD display for (i = 0; i < 9; i++) { PIR1bits.SSPIF = 0; // Clear SPI flag SSPBUF = LCDSTRING[i]; // Send byte while (!PIR1bits.SSPIF); // Wait for transmission to complete } PORTDbits.RD5 = 1; // Return RB5 high, ready for next string } /******************************* * LoopTime * * This function puts the chip to sleep upon entry. * It wakes up and executes a HPISR and then returns to sleep. * It wakes up and executes a LPISR and then exits. ******************************* */ void LoopTime() { while (!LPISRFLAG) // Sleep upon entry and upon exit from HPISR { // Return only if LPISR has been executed, Sleep(); // which sets LPISRFLAG Nop(); } LPISRFLAG = 0; // Sleep upon next entry to LoopTime }
FIGURE 10-7 (continued) RB2/INT2 pin exhibits a 75-s rise time after RE2 is raised to 3 V when the RPG is in the 11 position. Since RE2 will be left high thereafter, this 75-s rise time occurs every time the RPG is changed from its 00 position to its 11 position. The long rise time is what damps out any contact-bounce chatter when the RPG is changed from its 11 position to
Section 10.5
149
its 00 position. The INT2 input is not affected by the slow rise time, inasmuch as the signal on the input pin is subjected to a Schmitt trigger before being passed along to the interrupt circuitry. The Schmitt trigger converts its slowly rising input to a fast snap of a change on its output. It also subjects the input to hysteresis, having a slightly higher rising-voltage threshold at which the output snaps than its falling-voltage threshold at which the output snaps back. This hysteresis also mitigates the effect of any contactbounce chatter. The result is an RPG whose performance matches the ideal, with one increment of the displayed value for each detented click of the knob.
PROBLEMS
10-1 RPGcounter modification a) Modify the RPGcounter function of Figure 10-4 and its initialization to use two variables
RPGTENS and RPGONES
in place of RPGNUM. Each time DELRPG causes an increment or decrement of RPGNUM in the old RPGcounter function, now increment or decrement the ASCII-coded value in RPGTENS and RPGONES appropriately. Refer to T2.cs Time function in Figure 5-5 for ideas. b) Repeat for the RPGcounter function of Figure 10-6. 10-2 Polled RPG.c a) Modify the RPG function of Figure 10-3 to poll the two outputs of the detented RPG of Figure 10-5a. Because the 1-M pull-up resistor on the RB2 input produces such a long response time, initialize RE2 high and leave it high. b) Write a PolledRPG.c file derived from the RPG.c file of Figure 10-7 by removing the high-priority ISR and adding the RPG function of Part (a), called from the main loop. Also replace the RPGcounter function with that of Figure 10-3. c) Read an 8-bit value from the pot every 20th time around the main loop. Use this to modify the loop time proportional to the pot value and from (about) 1 ms if the pot is fully CCW and (about) 10 ms if the pot is fully CW. d) As the RPG is turned, either slowly or at a faster (but normal) rate, determine the maximum loop time for which RPG clicks (almost always) produce increments or decrements on the LCD. Measure the time between edges of RC2 to determine this loop time.
Chapter
11
MEASUREMENTS
11.1 OVERVIEW
Coding a microcontroller application in C has benefits and drawbacks, listed in Section 2.9. As mentioned there, the size of the resulting program is not a dominating issue, as long as it fits in the available program memory within the target microcontroller. The speed of execution of algorithms is another story, given the weight placed on coin cell current draw and the resulting effect on coin cell life. The faster execution of an algorithm leads directly to more sleep time (with the MCU drawing less than 2 A) and less awake time (with the MCU drawing more than 1 mA). In this chapter, the monitoring of both code size and execution time will be discussed. C coding provides little insight into either issue. Being able to monitor code size and execution time allows the comparison of alternative codings of an algorithm. From such a comparison, coding choices can be made intelligently.
Section 11.2
Code Size
151
In an environment where the C compiler may need to insert complex subroutines to implement a few lines of seemingly simple source code, this gross view of the code generated provides helpful insight. Example 11-1 Compare the amount of code generated by Figure 11-1a with the amount of code generated by the alternative implementation of the same function shown in Figure 11-1b. This latter implementation uses the same technique of incrementing or decrementing an ASCII-coded two-digit number as was used in the Pushbutton function of the T2.c template program (Figure 5-5). Solution Compiling the two source files containing the two versions of the RPGcounter produces program byte counts for each file. The program containing the code of Figure 11-1a generates 22 more bytes of code than the identical program with only the RPGcounter function replaced by the code of Figure 11-1b. This example illustrates several points: While the implementation of Figure 11-1b uses slightly less memory than the implementation of Figure 11-1a, it is more complicated to write and more difficult for someone else to understand. These are significant considerations. The amount of code generated does not correlate well with the number of lines in the source file. In the implementation of Figure 11-1a, the formation of LCDSTRING[6] incurs the call of a divide subroutine. The formation of LCDSTRING[7] has the compiler calling the same subroutine with the same divisor and dividend to form the (previously discarded) remainder. The algorithm of Figure 11-1b replaces these divides with increments, decrements, compares, and reinitializations.
/******************************* * RPGcounter * * This function uses DELRPG to update the signed char variable, RPGNUM. * DELRPG is returned to zero. * LCDSTRING[6] and LCDSTRING[7] display count. ******************************* */ void RPGcounter() { while (DELRPG > 0) { --DELRPG;
152
Chapter 11
Measurements
while (DELRPG < 0) { ++DELRPG; if (--RPGNUM < 0) { RPGNUM += 100; } LCDFLAG = 1; // Set flag to display } TEMPCHAR = RPGNUM; LCDSTRING[6] = '0' + (TEMPCHAR / 10); LCDSTRING[7] = '0' + (TEMPCHAR % 10); } (a) Implementation of RPGcounter using two divide operations
/******************************* * RPGcounter * * This function uses DELRPG to update ASCII-coded RPGTENS:RPGUNITS * DELRPG is returned to zero. * LCDSTRING[6] and LCDSTRING[7] display count. ******************************* */ void RPGcounter() { while (DELRPG > 0) { --DELRPG; if (++RPGUNITS > '9') { RPGUNITS = '0'; if (++RPGTENS > '9') { RPGTENS = '0'; } } LCDFLAG = 1; // Set flag to display } while (DELRPG < 0) { ++DELRPG;
Section 11.3
Code Execution
153
if (--RPGUNITS < '0') { RPGUNITS = '9'; if (--RPGTENS < '0') { RPGTENS = '9'; } } LCDFLAG = 1; // Set flag to display } LCDSTRING[6] = RPGTENS; LCDSTRING[7] = RPGUNITS; } (b) Implementation of RPGcounter incrementing ASCII-coded RPGTENS:RPGUNITS. // Update display string
154
Chapter 11
Measurements
/******************************* * Macro definitions ******************************* */ #define Disable() INTCONbits.GIE = 0 #define Start() PORTBbits.RB0 = 1 #define Stop() PORTBbits.RB0 = 0 #define Enable() INTCONbits.GIE = 1 (a) Definition of Start() and Stop() macros. Disable(); Start(); LCDSTRING[0] = '0' + (TEMPCHAR / 10); LCDSTRING[1] = '0' + (TEMPCHAR % 10); Stop(); Enable(); (b) Formation of two ASCII-coded digits. Execution time = 210 1 = 209 s.
Section 11.5
155
// Pause for 1 ms
(a) One-millisecond pause in main loop. /******************************* * HiPriISR * * Measure time CPU digresses from main to execute this minimal HPISR. ******************************* */ void HiPriISR() { PIR2bits.TMR3IF = 0; // Clear interrupt flag } (b) A minimal high-priority interrupt service routine.
FIGURE 11-4 Determining the minimum time overhead of a high-priority interrupt service routine.
156
Chapter 11
Measurements
Section 11.7
Cleaning up Surprises
157
more offending functions can be identified. Testing makes all the difference, whereas looking at a long source file is a difficult method of debugging. As once pointed out to the author, paper never protests. Some of the issues that can arise are: A function may calculate a value using long-duration multiplies and divides each time around the main loop while the value is used to update the display only once every 10 loop times. An interrupt service routine that occurs many times per 10-ms loop time also carries out an extensive routine to handle each occurrence. Instead, it might accumulate successive changes in a variable and then let a main function handle the accumulated change each loop time before resetting the variable. Alternative implementations of a function may yield quite different execution times. It is difficult to discern this from the source file. It is easy to discern it by the measurement of the alternatives. Time is not spent looking for alternatives to code that only looked like it might produce a long execution time, when, in fact, it does not.
PROBLEMS
11-1 Function size Consider the T3.c template of Figure 7-6. Determine the number of program bytes generated by the Pushbutton function. First, compile the code as is and note the number of program bytes generated. Then form a T3A.c template with /* inserted before the line
void Pushbutton()
and a */ after the functions final }. Then recompile and note the reduced number of program bytes generated. The difference between these two numbers is the number of bytes sought. 11-2 Function size Form a T3B.c template program by rewriting the Pushbutton function of Figure 7-6 using a new signed char variable, PBNUM, to represent the number of pushbutton pushes. Use the code of Figure 11-1a as a model for how to use PBNUM, including the final three lines of code
TEMPCHAR = PBNUM; LCDSTRING[0] = '0' + (TEMPCHAR / 10); LCDSTRING[1] = '0' + (TEMPCHAR % 10);
a) Compare the number of program bytes generated by T3B.c with the number found for T3A.c of Problem 11-1, for which the Pushbutton code was commented out.
158
Chapter 11
Measurements
11-3 Execution time a) In the main function of T3.c, insert the Disable and Start macros before
Pushbutton();
and the Stop and Enable macros after it. Recompile and measure the execution time of the Pushbutton function. What is the value when the pushbutton is not pressed? What is the value when the number changes from 2 to 3? From 9 to 10? b) Repeat for T3B.c.
Chapter
12
INTERRUPTS
12.1 OVERVIEW
Interrupts provide a way for the CPU to break out of the lockstep sequence of tasks dictated by the function calls in the main loop. Stepping the stepper motor with a step period other than some multiple of the loop time represents a common interrupt application. Sensing RPG motion only when the RPG generates edges represents another. How interrupt service routines are written in C has a profound effect on both execution time and program size. The first part of this chapter pinpoints this issue. The remainder of the chapter identifies the control and status bits associated with every interrupt source in the PIC18LF4321.
159
160
Chapter 12
Interrupts
LoPriISR, were written and added to the source file just like any other function. When an interrupt of main program execution occurs, the CPU: 1. Suspends its main program execution. 2. For a low-priority interrupt, further low-priority interrupts are disabled, but any high-priority interrupts that have been enabled are left enabled. For a highpriority interrupt, all interrupts are disabled. 3. Vectors to the interrupt service routine. 4. Copies any CPU registers that, if changed by the execution of the interrupt service routine, might corrupt the execution of the interrupted main program. 5. Executes the interrupt service routine. 6. Restores the CPU registers set aside in Step 4. 7. Reenables the interrupt level (high or low) that was disabled in Step 2. 8. Returns to the main program at the point where execution was suspended in Step 1.
Section 12.3
161
// Initialize everything
// // // //
Set pin high Pause for 140 microseconds Set pin low Pause for 200 microseconds
(a) Setup to measure the digression from a constant duration pulse by an interrupt.
//
PIE1bits.TMR1IE = 1; PIE2bits.TMR3IE = 1;
(b) Lines from Initial function, for enabling only one interrupt source at a time.
/******************************* * HiPriISR * * Reload TMR3H, clear flag, and return. ******************************* */ void HiPriISR() { TMR3H = 0xFF; PIR2bits.TMR3IF = 0; // Clear interrupt flag } /******************************* * LoPriISR * * Reload TMR1H, clear flag, and return. ******************************* */ void LoPriISR() { TMR1H = 0xFF; PIR1bits.TMR1IF = 0; // Clear interrupt flag } (c) One way of writing ISRs (from IntProg1.c in www.qwikandlow.com)
162
Chapter 12
Interrupts
/******************************* * HiPriISR ******************************* */ void HiPriISR() { Timer3Handler(); } /******************************* * LoPriISR ******************************* */ void LoPriISR() { Timer1Handler(); } /******************************* * Timer3Handler ******************************* */ void Timer3Handler() { TMR3H = 0xFE; // Reload Timer3 PIR2bits.TMR3IF = 0; // Clear interrupt flag } /******************************* * Timer1Handler ******************************* */ void Timer1Handler() { TMR1H = 0xFE; // Reload Timer1 PIR1bits.TMR1IF = 0; // Clear interrupt flag } (d) Another way of writing ISRs (from IntProg2.c in www.qwikandlow.com).
FIGURE 12-1 (continued) the 3 s to reload the timer and clear the flag. Thus, when the code of Figure 12-1c is executed with only Timer3 producing high-priority interrupts, the 140-s pulse is extended 14 s by an intervening interrupt. The ISR overhead of 11 s (i.e., 14 s 3 s) is listed in the top line of Figure 12-2. The results of all four experiments are summarized in this table of Figure 12-2. The second line shows an ISR overhead of 21 s. The 10 s less taken by the HiPriISR to do the same thing occurs because the high-priority ISR automatically and quickly copies three CPU registers into three shadow registers on entry and quickly restores them on exit. The low-priority ISR must also set aside and later restore these same
Section 12.4
163
ISR overhead HiPriISR of Figure 12-1c LoPriISR of Figure 12-1c HiPriISR of Figure 12-1d LoPriISR of Figure 12-1d 11 21 = (11 + 10) 99 = (11 + 88) 109 = (21 + 88)
FIGURE 12-2 Effect of interrupt service routine code writing alternatives on performance three CPU registers, but there is no automatic, fast mechanism to do so. Rather, the compiler adds instructions to do the setting aside and restoring for the low-priority interrupt service routine. When the two source code lines in an interrupt service routine, to reload the timer and clear the flag, are broken out to a separate handler function as in Figure 12-1d, evidently the C compiler loses track of what registers the service routine will be using. As shown in the third and fourth lines of Figure 12-2, the compiler adds 276 bytes of code. This extra code evidently sets aside and restores every conceivable CPU register and compiler-generated variable that might ever be corrupted by an interrupt service routine. When this code is executed, it also adds 88 s to the interrupt service routines execution time. These four tests provide the insight that a large performance penalty is incurred for both the program code size and execution time by delegating interrupt service routine code to a separate handler function.
164
Chapter 12
Interrupts
// Initialize everything
// // // //
Set pin high Pause for 140 microseconds Set pin low Pause for 200 s
(a) Setup to measure the digression from a constant duration pulse by an interrupt. T1CONbits.TMR1ON = 1; T3CONbits.TMR3ON = 1; INTCON3bits.INT2IE = 1; // Enable Timer1 // Enable Timer3 // Enable INT2 interrupt source
// //
(b) Lines from Initial function, for disabling all but one interrupt source. /******************************* * HiPriISR ******************************* */ void HiPriISR() { if (PIR2bits.TMR3IF) // Timer3 interrupt? { TMR3H = 0xFE; PIR2bits.TMR3IF = 0; } if (PIR1bits.TMR1IF) // Timer1 interrupt? { TMR1H = 0xFE; PIR1bits.TMR1IF = 0; } if (INTCON3bits.INT2IF) // INT2 interrupt? { INTCON3bits.INT2IF = 0; } } (c) A third way of writing ISRs (from IntProg3.c in www.qwikandlow.com). Compiles to 423 bytes. CPU digresses for 22-3 = 19 us to handle HiPriISR for Timer3. CPU digresses for 22-3 = 19 us to handle HiPriISR for Timer1. CPU digresses for 20-1 = 19 us to handle HiPriISR for INT2. (d) Results
Section 12.7
External Interrupts
165
In addition, each interrupt source has its own local interrupt control bits: A priority control bit A local enable bit A local flag bit To have any interrupt source be given high-priority interrupt servicing, its priority control bit must first be set. Alternatively, the priority control bit must be cleared to have it responded to as a low-priority interrupt source. Each interrupt source has a local enable bit that is disabled at reset. Consequently, nothing need be done to initialize the many possible interrupt sources that go unused in any given application. Figure 12-4 lists each possible interrupt source and identifies the register and bit names for the priority control bit, the local enable bit, and the local flag bit.
166
Chapter 12
Interrupts
Name INT0 external interrupt INT1 external interrupt INT2 external interrupt RB port change interrupt TMR0 overflow interrupt TMR1 overflow interrupt TMR3 overflow interrupt TMR2 matches PR2 interrupt CCP1 interrupt CCP2 interrupt A/D converter interrupt USART receive interrupt USART transmit interrupt Synchronous serial port int. I C bus collision interrupt Parallel slave port int. High/low-voltage detect int. Oscillator failure int. Comparator interrupt Data EEPROM write done int.
2
Priority Bit * INTCON3bits.INT1IP INTCON3bits.INT2IP INTCON2bits.RBIP INTCON2bits.TMR0IP IPIR1bits.TMR1IP IPR2bits.TMR3IP IPR1bits.TMR2IP IPR1bits.CCP1IP IPR2bits.CCP2IP IPR1bits.ADIP IPR1bits.RCIP IPR1bits.TXIP IPR1bits.SSPIP IPR2bits.BCLIP IPR1bits.PSPIP IPR2bits.HLVDIP IPR2bits.OSCFIP IPR2bits.CMIP IPR2bits.EEIF
Local Enable Bit INTCONbits.INT0IE INTCON3bits.INT1IE INTCON3bits.INT2IE INTCONbits.RBIE INTCONbits.TMR0IE PIE1bits.TMR1IE PIE2bits.TMR3IE PIE1bits.TMR2IE PIE1bits.CCP1IE PIE2bits.CCP2IE PIE1bits.ADIE PIE1bits.RCIE PIE1bits.TXIE PIE1bits.SSPIE PIE2bits.BCLIE PIE1bits.PSPIE PIE2bits.HLVDIE PIE2bits.OSCFIE PIE2bits.CMIE PIE2bits.EEIE
Local Flag Bit INTCONbits.INT0IF INTCON3bits.INT1IF INTCON3bits.INT2IF INTCONbits.RBIF INTCONbits.TMR0IF PIR1bits.TMR1IF PIR2bits.TMR3IF PIR1bits.TMR2IF PIR1bits.CCP1IF PIR2bits.CCP2IF PIR1bits.ADIF PIR1bits.RCIF PIR1bits.TXIF PIR1bits.SSPIF PIR2bits.BCLIF PIR1bits.PSPIF PIR2bits.HLVDIF PIR2bits.OSCFIF PIR2bits.CMIF PIR2bits.EEIF
(a) Initialization of PORTB pins as inputs or outputs RCONbits.IPEN = 1; INTCONbits.GIEL = 1; INTCONbits.GIEH = 1; // Enable high/low priority mechanism // Globally enable low-priority interrupts // Globally enable high-priority interrupts (b) Global interrupt initialization
Section 12.8
PortB-Change Interrupts
167
// Sense rising (1) or falling (0) edge // Local enable for INT0 interrupts // Clear flag initially, test flag subsequently
(c) INT0 interrupt initialization (high priority only) INTCON2bits.INTEDG1 = 1; INTCON3bits.INT1IP = 1; INTCON3bits.INT1IE = 1; INTCON3bits.INT1IF = 0; // // // // Sense rising (1) or falling (0) edge High (1) or low (0) priority selection Local enable for INT0 interrupts Clear flag initially, test flag subsequently
(d) INT1 interrupt initialization INTCON2bits.INTEDG2 = 1; INTCON3bits.INT2IP = 1; INTCON3bits.INT2IE = 1; INTCON3bits.INT21IF = 0; // // // // Sense rising (1) or falling (0) edge High (1) or low (0) priority selection Local enable for INT0 interrupts Clear flag initially, test flag subsequently
will clear the flag. If both RB4 and RB5 are set up as inputs, there may be an ambiguity concerning which input changed, setting RBIF. A narrow 0 1 0 or 1 0 1 pulse that is shorter than the response time to a resulting interrupt will have trouble identifying which pin caused the interrupt (if that matters).
168
Chapter 12
Interrupts
Normally, RB6 and RB7 can be set up as inputs to serve as two more interrupt-onchange pins. However, with QwikBug installed, RB6 and RB7 are taken over by the chips debug mode, features of which are used by QwikBug.
PROBLEMS
12-1 Measure HiPriISR of Figure 12-1c Modify the Measure.c program of Chapter Six to determine how long the CPU digresses from its execution of the main loop code to deal with the HiPriISR of Figure 12-1c. Your program should never stop. Rather, it should loop with timing such that one (but not more than one) interrupt is as likely as not to occur during each pass around the loop. Your main loop code should look like that of Figure 12-1a, but with the setting of RC2 replaced by the Start Function and the clearing of RC2 replaced by the Stop and Send functions. Modify the duration of the Delay macros to obtain an interrupt between Start and Stop as often as not. 12-2 Measure HiPriISR of Figure 12-1d Repeat the procedure of Problem 12-1 for the HiPriISR of Figure 12-1d. 12-3 Determining what affects execution time of HiPriISR Modify the code and measurement of Problem 12-2 by introducing a new char variable, NUMBER, that is multiplied by five: a) b) c) d) Only in the Initial function. Only in the main loop, but not between Start and Stop. Only in the HiPriISR before it calls Timer3Handler. Only in Timer3Handler.
Consider the measurement result in each case, compared with the measurement result for Problem 12-2 and explain any difference and what must be happening. Note that the multiply instruction employs the CPU registers PRODH and PRODL.
Chapter
13
TIMING MEASUREMENTS REVISITED (CALIBRATE.c)
13.1 OVERVIEW
Chapter Eleven explored various timing measurements with the help of an oscilloscope. This chapter will use Timer0 and Timer1 to calibrate FCPU, the nominal 1-MHz clock, against the 50 parts per million, 32,768-Hz Timer1 oscillator. Incrementing/decrementing the OSCTUNE register can then bring the actual value of FCPU close to 1 MHz. Alternatively, any time interval measurement can be scaled from timer counts to microseconds using a previously determined calibration factor. Either of the MCUs two CCP (capture, compare, PWM) modules can be used in conjunction with Timer3 to measure the duration of a pulse input to a CCP pin. Because the duration is measured as the number of CPU cycles occurring between the leading and trailing edges of the pulse, the time of each edge has the 1-s resolution of the CPU clock. The duration can be calibrated as it is converted from CPU cycles to microseconds. For measuring the execution time of an algorithm, it is the cycle count that provides the definitive measure, not the microseconds. The Start, Stop, and Send functions first used in Chapter Six are developed at the end of this chapter.
169
170
Chapter 13
Note that the read of the 16-bit counter takes place during the read of TMR0L in the first line. The write back into the 16-bit counter takes place during the write to TMR0L in the fourth line. Thus, the addition of NUMH:NUML into TMR0H:TMR0L while it continues to run, will result in somewhat less of a change in TMR0H:TMR0L than the number of counts in NUMH:NUML. For example, adding 0x0000 sets the counter back to what was in it when it was read with the first line of the code sequence listed above. The use of Timer0 in the next section will actually start and stop the counter. Nevertheless, the buffering with TMR0H makes it imperative that the bytes be read (or written to) in the order dictated by Figure 13-1. Before leaving this section, consider the Timer0 use shown in Figure 13-2. With T0CON initialized as shown, Timer0 is incremented by each rising edge into the Qwik&Low boards uncommitted RA4/T0CKI pin. The synchronizer assumes the edges of the input waveform occur at a slower rate than FOSC. The synchronizer delays each input edge until the time in each CPU cycle when the circuit of Figure 13-1 increments the counter. Because of this, TMR0L can be read, or written to, by the CPU reliably.
8-bit counter Write to TMR0L Read from TMR0L TMR0H buffer register TMR0IF
FCPU
set
TMR0ON T0CON
INTCON
FIGURE 13-1 Timer0 use for counting FCPU (1 MHz) clock periods
171
172
8-bit counter Write to TMR0L Read from TMR0L T0CON TMR0H buffer register TMR0IF
set
TMR0ON
Chapter 13
INTCON
FIGURE 13-2 Timer0 use for counting input events (i.e, edges)
Section 13.3
173
OSCTUNE 0 0 1 1 1 1
Maximum frequency
0 0 0 0 1 0 0 0 0 0 1 1 1 1 1
1 0 0 0 0
Minimum frequency Unimplemented bit 31.25 kHz source (see Figure 2-3)
Timer0 also includes a prescaler option (not shown in either figure) for dividing the counter input edges by any multiple of 2 from 2 to 256. For information on this (or any) option, refer to the data manual on the www.microchip.com web site.
Alternatively, if CALIB is less than 7,812, the internal oscillator is slow. It can be speeded up by incrementing the OSCTUNE register, as indicated in Figure 13-3. Successive increments or decrements of OSCTUNE to minimize the deviation from 7,812 may make the value of a TIME measurement sufficiently accurate for a specific purpose without resorting to Equation 13-1. The calibration strategy suggested in this section makes use of the revised organization of program code shown in Figure 13-4. A calibration supervisor function, CalSup1, is inserted at the beginning of the main loop and another, CalSup2, is inserted at the end, just before the call of LoopTime. A state variable, CAL, lets each of these
174
Chapter 13
Power on
CalSup1
CalSup2
LoopTime
FIGURE 13-4 Reorganizing main loop for calibrating Fosc two functions know where they are in the calibration process, from one loop to the next. Normally, CAL = 0 and neither function does anything more than just return. When Initial, or subsequently a main loop task, wants to initiate a calibration, it sets CAL = 1 (Ready). Figure 13-5 shows how the subsequent calibration sequence plays out. When CalSup1 is next called (with CAL = 1), it stops and resets Timer0 to zero. It stops Timer1 and reloads it with 0xFFFE, clears its overflow flag, and enables its counting of the slow 32,768-Hz Timer1 clock again (having not missed a clock edge). When Timer1 has progressed two more counts and rolls over, Timer0 is started (Go). TMR1H is reloaded with 0xFF and its overflow flag is cleared. Then CAL is set to 2 so that the CalSup2 function will be there waiting for the rollover of Timer1 after exactly 256 clock edges from the Timer1 oscillator. After the main loop tasks have been completed for this pass around the loop, CalSup2 is called. It sees CAL = 2 and takes charge of the calibration process. Instead of the chip returning to sleep immediately, CalSup2 waits for TMR1IF to be set, at which time, Timer0 is stopped. Meanwhile, Timer1 is 2 + 256 counts on its way to the 328 counts that define the 10-ms loop time. So Timer1 is stopped, 0xBA is added to TMR1L, Timer1 is started again, and TMR1H is reloaded with 0xFF so that the next Timer1 overflow will occur 10 ms from the beginning of the loop, analogous to
Section 13.3
CalSup2 sends display string to PC CalSup2 completes measurement CalSup1 (Calibration supervisor) starts measurement
Main loop task calls for calibration by setting CAL = 1 0 1 Sleep Sleep 2 3 Sleep 0 Sleep Sleep
CPU activity
CalSup1
CalSup2
Timer1 counts
Time
175
176
Chapter 13
what is done by the LoPriISR of T3.c in Figure 7-6. Timer0 is read and used to form an unsigned int variable, CALIB, with
CAL1BL = TMR0L; CALIBH = TMR0H; // Read in correct sequence // into unsigned int variables // Form CALIB = CALIBH:CALIBL
Finally, CAL is set to 3 and the TMR1IF flag is reset. On returning from the CalSup2 function, the CPU calls the LoopTime function and returns to sleep for the remainder of the 10-ms loop time, as shown in Figure 13-5. On the next pass around the main loop, CalSup2 converts CALIB to its four-digit ASCII representation and sends it to the PC for display. The Calibrate.c file of Figure 13-6 illustrates the entire process. A PBcalibrate function is included that increments Figure 13-3s OSCTUNE each time the pushbutton is pushed. With OSCTUNE initialized to 0b00011111, the first press will produce the power-on default center frequency.
/******* Calibrate.c *********** * * Use Fosc = 4 MHz for Fcpu = Fosc/4 = 1 MHz. * Calibrate Fosc each time that the pushbutton is pressed, starting with * the nominal value and incrementing OSCTUNE with each subsequent press. * Display CALIB on PC. CALIB = 7812 if Fosc/4 = 1 MHz exactly. * Use Timer1 to set loop time to 10 ms. * Blink LED on RD4 for 10 ms every 2.5 seconds. * ******* Program hierarchy ***** * * main * Initial * InitTX * CalSup1 * CalSup2 * BlinkAlive * PBcalibrate * LoopTime * ******************************* */ #include <p18f4321.h> /******************************* * Configuration selections ******************************* */ #pragma config OSC = INTIO1 #pragma config PWRT = ON // Use internal osc, RA6=Fosc/4, RA7=I/O // Enable power-up delay // Define PIC18LF4321 registers and bits
Section 13.3
177
LVP = OFF WDT = OFF WDTPS = 4 MCLRE = ON PBADEN = DIG CCP2MX = RB3 BOR = SOFT BORV = 3 LPT1OSC = OFF
// // // // // // // // //
Disable low-voltage programming Disable watchdog timer initially 16 millisecond WDT timeout period, nominal Enable master clear pin PORTB<4:0> = digital Connect CCP2 internally to RB3 pin Brown-out reset controlled by software Brown-out voltage set for 2.0V, nominal Deselect low-power Timer1 oscillator
/******************************* * Global variables ******************************* */ unsigned char ALIVECNT; // signed char i; // unsigned int DELAY; // unsigned int TIMEL; // unsigned int TIMEH; // unsigned int CALIB; // unsigned char CAL; // char OLDPB; // char NEWPB; // char PCSTRING[] = "xxxx"; // /******************************* * Function prototypes ******************************* */ void void void void void void void Initial(void); InitTX(void); BlinkAlive(void); CalSup1(void); CalSup2(void); PBcalibrate(void); LoopTime(void);
Scale-of-400 counter for blinking "Alive" LED Index into strings Sixteen-bit counter used by Delay macro Int version of TMR0L Int version of TMR0H Calibration constant Calibration state variable Old pushbutton state New pushbutton state String to represent 0 - 9999 value
/******************************* * Macros ******************************* */ #define Delay(x) DELAY = x; while(--DELAY){ Nop(); Nop(); } #define TXascii(in) TXREG = in; while(!TXSTAbits.TRMT) /////// Main program ////////////////////////////////////////////////////////// /******************************* * main ******************************* */
178
Chapter 13
void main() { Initial(); // Initialize everything InitTX(); // Initialize UART's TX output while (1) { CalSup1(); // First part of calibration supervisor PORTCbits.RC2 ^= 1; // Toggle for looptime BlinkAlive(); // Blink "Alive" LED PBcalibrate(); // Calibrate each time Pushbutton is pressed Delay(600); // Pause CalSup2(); LoopTime(); // Sleep, letting watchdog timer wake up chip } } /******************************* * Initial * * This function performs all initializations of variables and registers. ******************************* */ void Initial() { OSCCON = 0b01100010; ADCON1 = 0b00001011; TRISA = 0b00001111; TRISB = 0b01000100; TRISC = 0b10000000; TRISD = 0b10000000; TRISE = 0b00000010; PORTA = 0; PORTB = 0; PORTC = 0; PORTD = 0b00100000; PORTE = 0; Delay(50000); RCONbits.SBOREN = 0; ALIVECNT = 247; OLDPB = 0; NEWPB = 0; CAL =0; OSCTUNE = 0b00011111; PIE1bits.TMR1IE = 1; TMR1H = 0xFF; TMR1L = 0x00; T1CON = 0b01001111; PIR1bits.TMR1IF = 0; INTCONbits.GIEL = 1; }
// // // // // // // //
Use Fosc = 4 MHz (Fcpu = 1 MHz) RA0,RA1,RA2,RA3 pins analog; others digital Set I/O for PORTA Set I/O for PORTB Set I/O for PORTC Set I/O for PORTD Set I/O for PORTE Set initial state for all outputs low
// except RD5 that drives LCD interrupt // // // // // // // // // // // // Pause for half a second Now disable brown-out reset Blink immediately Initialize pushbutton flags Calibration state variable - do nothing yet CALIB = default value after first PB push Enable local interrupt source Initial value Timer1 runs from 32768 Hz oscillator Clear Timer1 flag Enable wake-up from sleep
Section 13.3
179
/******************************* * InitTX * * This function initializes the UART for its TX output function. It assumes * Fosc = 4 MHz. For a different oscillator frequency, use Figure 6-3c to * change BRGH and SPBRG appropriately. ******************************* */ void InitTX() { RCSTA = 0b10010000; TXSTA = 0b00100000; SPBRG = 12; BAUDCON = 0b00111000; }
// // // //
/******************************* * BlinkAlive * * This function briefly blinks the LED every four seconds. * With a looptime of about 10 ms, count 250 looptimes. ******************************* */ void BlinkAlive() { PORTDbits.RD4 = 0; if (++ALIVECNT == 250) { ALIVECNT = 0; PORTDbits.RD4 = 1; } }
// Turn off LED // Increment counter and return if not 250 // Reset ALIVECNT // Turn on LED for one looptime
/******************************* * PBcalibrate * * Calibrate each time pushbutton is pressed with an increase calibration value ******************************* */ void PBcalibrate() { PORTEbits.RE0 = 1; // Power up the pushbutton Nop(); // Delay one microsecond before checking it NEWPB = !PORTDbits.RD7; // Set flag if pushbutton is pressed PORTEbits.RE0 = 0; // Power down the pushbutton if (!OLDPB && NEWPB) // Look for last time = 0, now = 1 { CAL = 1; // Initiate a calibration sequence
180
Chapter 13
/******************************* * CalSup1 * * First part of calibration supervisor. * ******************************* */ void CalSup1() { if (CAL == 1) { T0CON = 0b00001000; // Timer0 stopped TMR0H = 0; // Timer0 = 0 TMR0L = 0; T1CON = 0b01001110; // Stop Timer1 TMR1H = 0xFF; // Wait for exact rollover TMR1L = 0xFE; PIR1bits.TMR1IF = 0; // Clear flag T1CONbits.TMR1ON = 1; // Start Timer1 again while (!PIR1bits.TMR1IF); // and wait for it to set again T0CONbits.TMR0ON = 1; // Start Timer0 TMR1H = 0xFF; // Next overflow in 256 counts of Timer1 PIR1bits.TMR1IF = 0; // Clear flag (if not already cleared) CAL = 2; } } /******************************* * CalSup2 * * Second part of calibration supervisor. * ******************************* */ void CalSup2() { if (CAL == 2) // Collect measurement { while (!PIR1bits.TMR1IF); // Wait for Timer1 to roll over and set flag T0CONbits.TMR0ON = 0; // Stop Timer0 T1CONbits.TMR1ON = 0; // Pause Timer1 counter TMR1L += 0xBB; // Cut out remaining counts of Timer1 T1CONbits.TMR1ON = 1; // Resume Timer1 counter TMR1H = 0xFF; // Upper byte of Timer1 will be 0xFF PIR1bits.TMR1IF = 0; // Clear interrupt flag TIMEL = TMR0L; // Read Timer0 in correct sequence
Section 13.4
181
TIMEH = TMR0H; CALIB = (TIMEH << 8) +TIMEL; // Form calibration factor CAL = 3; // Done with calibration sequence } else if (CAL == 3) { for (i = 3; i >= 0; --i) { PCSTRING[i] = (CALIB % CALIB = CALIB / 10; } for (i = 0; i <= 3; ++i) { TXascii(PCSTRING[i]); } TXascii(0x0D); TXascii(0x0A); CAL = 0; } } /******************************* * LoopTime * * This function puts the chip to sleep, to be awakened by Timer1 rollover. ******************************* */ void LoopTime() { Sleep(); Nop(); T1CONbits.TMR1ON = 0; // Pause Timer1 counter TMR1L += 0xB9; // Cut out all but 328 counts of Timer1 T1CONbits.TMR1ON = 1; // Resume Timer1 counter TMR1H = 0xFE; // Upper byte of Timer1 will be 0xFE PIR1bits.TMR1IF = 0; // Clear interrupt flag } // Display result // Form digits and display on PC 10) + 0x30;
182
TRISCbits TRISC2 = for CCP1 input or TRISBbits TRISB3 = 1 for CCP2 input T3CON = 0b01001001 to clock Timer3 from FCPU (1 MHz) CCP1CON = 0b00000100 for falling edge or 0b00000101 for rising edge on CCP1 pin or CCP2CON = 0b00000100 for falling edge or 0b00000101 for rising edge on CCP2 pin CCPR1 16 bits CCP1IF set Edge select CCP1M0 CCP1CON 0 0 0 0 0 1 0 1 TMR3 16 bits FCPU (1 MHz) CCP2CON 0 0 0 0 0 1 0 0 CCP2M0 1: Rising edge 0: Falling edge Edge select PIR2 16 bits set CCP2IF CCP2/RB3 CCP1/RC2 1: Rising edge 0: Falling edge PIR1
Chapter 13
CCPR2
Section 13.5
183
to continue clocking TMR3), CCPR1 is copied to an unsigned int TRAILINGEDGE variable. Then
TIME = TRAILINGEDGE LEADINGEDGE;
Note that with all three variables being declared to be unsigned integers, the difference will be correct even if Timer3 rolls over between the leading edge capture and the trailing edge capture. For example, the hex subtraction 0002 FFFF produces a difference of 0003 (plus a borrow that is lost to the 16-bit unsigned result). This capture mode determination of an input pulse width produces a timing resolution of 1 s for each edge. However, it is important to read the captured time before the same edge occurs again and overwrites the previously captured time. Because the flag bits can be set up to interrupt the CPU, the CPU can actually be undertaking other tasks while the measurement is under way. However, the CPU cannot be allowed to sleep during the measurement because that will stop the 1-MHz clocking of Timer3.
PROBLEMS
13-1 Adding into Timer0 A user, unaware of the buffering of TMR0H shown in Figure 13-1, might make the mistake of adding NUMH:NUML to TMR0H: TMR0L with
TMR0L += NUML; TMR0H += (NUMH + STATUSbits.C);
184
Chapter 13
/******************************* * Start * * This function clears Timer0 and then starts it counting. ******************************* */ void Start() { T0CON = 0b00001000; // Set up Timer0 to count CPU clock cycles TMR0H = 0; // Clear Timer0 TMR0L = 0; T0CONbits.TMR0ON = 1; // Start counting } (a) Start function. /******************************* * Stop * * This function stops counting Timer0, and reads the result into CYCLES. ******************************* */ void Stop() { T0CONbits.TMR0ON = 0; // Stop counting CYCLES = TMR0L; // Form CYCLES from TMR0H:TMR0L CYCLES += (TMR0H * 256); CYCLES -= 3; // Remove 3 counts so back-to-back Start-Stop } // functions produce CYCLES = 0 (b) Stop function. /******************************* * Send * * This function converts CYCLES to four ASCII-coded digits and sends * the result to the PC for display. ******************************* */ void Send() { BIGNUM = CYCLES; // Load ASCII4's input parameter ASCII4(); // Convert TXascii('\r'); // Send carriage return TXascii('\n'); // Send line feed TXascii(THOUSANDS); // Send four-digit number TXascii(HUNDREDS); TXascii(TENS); TXascii(ONES); } (c) Send function.
Section 13.5
185
13-2 Clock-edge synchronization A look at the .lst file of some code that employs the Start and Stop functions of Figure 13-8 shows that a two-cycle subroutine return instruction follows the setting of the TMR0ON bit at the end of the Start function. A two-cycle subroutine call instruction is executed before the TMR0ON bit is cleared at the beginning of the Stop function. Noting that the setting of a port pin immediately followed by the clearing of the pin produces a 1-s pulse, so the setting of TMR0ON immediately followed by the clearing of TMR0ON ought to produce a count of one in the previously cleared Timer0 counter. Consequently, the back-to-back calls of the Start and Stop functions of Figure 13-8 ought to produce a count of 2 + 1 + 2 = 5 and require a correction of
CYCLES = 5;
not the
CYCLES = 3;
shown in the Stop function. The two-cycle difference occurs because of a clock synchronizer circuit. This circuit is shown in Figure 13-2 because of its important role in dealing with an unsynchronized clock input, T0CKI. Because it is a distraction, it has been left out of the simplified schematic of Figure 13-1. However, unlike the clock synchronizer associated with Timer1, it cannot be bypassed via the setting or clearing of a bit in a control register. To verify the loss of the first two counts after TMR0ON is set, write a little test program that clears TRM0H, clears TMR0L, sets TMR0ON, clears TMR0ON, and then displays TMR0L. Repeat this by inserting one Nop macro, then two Nop macros, and finally three Nop macros between the setting and clearing of the TMR0ON bit. What is the result in each case, and how do you (now) explain it? 13-3 Oscillator calibration Run the Calibrate.c program. a) What is the frequency increment corresponding to a single increment of the OSCTUNE register? b) What value of OSCTUNE will produce the integer value of CALIB that is closest to 7,812.5, the value corresponding exactly to FCPU = 1.0000 MHz? c) Form a new CalibrateX.c program that uses the RPG to increment or decrement the five frequency controlling bits of OSCTUNE. Use the pushbutton to determine a new value of CALIB. Display on the LCD both of these values, using the format
D CCCC
where D represents the signed frequency-setting number in OSCTUNE (i.e., 1, 0, +1, etc.) and CCCC represents the value of CALIB. Whenever the RPG is turned, blank CCCC until the pushbutton is next pressed.
186
Chapter 13
d) Use CalibrateX.c to tune the oscillator as close to 7,812 as possible. Then, without changing the tuning, record CALIB immediately and 1 and 2 min later. e) Create a CalibrateY.c program that, in contrast to CalibrateX.c, never sleeps. That is, modify the LoopTime function so that it just waits for Timer1 to roll over before reloading the value in Timer1. The intent is to then repeat Part (d) with the chip drawing an average current of about a milliampere. See whether any attendant heating within the chip affects the calibration. 13-4 External Time Measurement The Display function that is used throughout this book clears RD5, sends the nine ASCII-coded characters in LCDSTRING to the LCD, and then sets RD5. Connect the TP10 test point (RD5) to the RC2/CCP1 pin on the H4 proto area header. Then write a little test program that uses the CCP1 circuit of Figure 13-7 to capture the number of CPU clock cycles between the falling edge and the rising edge. Send the resulting number to QwikBug for display in its Console window. 13-5 Start, Stop, and Send functions Repeat Problem 13-4 by modifying the Display function with the Start, Stop, and Send functions to make the same measurement by counting cycles from the instruction that clears RD5 at the beginning of Display to the instruction that sets RD5 at the end. Then display the result on the PC.
Chapter
14
EEPROM (EETEST.c)
14.1 OVERVIEW
The PIC18LF4321 contains 256 bytes of data EEPROM, hereafter referred to as EEPROM. This is nonvolatile memory that can be written to a byte at a time, the same as a RAM location. Reads from the EEPROM take just one CPU cycle, the same as RAM. In contrast to RAM, EEPROMs nonvolatility means that its data will be retained even when power is removed from the chip and then subsequently restored. Also, unlike RAM, for which a write operation takes just one cycle, a write to a byte of EEPROM typically takes 4 ms, with the write operation being self-timed by the EEPROM module. This chapter describes EEPROM use.
188
Chapter 14
EEPROM (EEtest.c)
Industrial: instruments, access cards, motor control, alarms, . . . Computer: motherboards, wireless optical mice, hard drives, . . . Many applications require much more that the 256 bytes of EEPROM available in the PIC18LF4321, and instead typically use 1 Kbit1 Mbit EEPROM parts having a serial interface. Many other applications are served well by a small number of bytes. One example is the exposure calibration adjustment built into a digital camera. Another is channel selection information built into a TV remote. When an oscilloscope has the ability to retain and restore a complicated setup state, this indicates the presence of a small EEPROM. When a lawn sprinkler controller remembers its settings through a power glitch or a power outage, this points to a small EEPROM. When the Qwik&Low board powers up and retrieves the most recently determined clock calibration byte from EEPROM and loads it in the OSCTUNE register, this represents a use of the PIC18LF4321s built-in EEPROM.
Section 14.3
189
EECON1 0 0 0 0 RD
Setting RD initiates an EEPROM read. The RD bit is automatically cleared. The RD operation takes one CPU cycle. Used by the 3-line sequence of Section 14.2 to initiate a self-timed 4-millisecond write cycle. The WR bit is automatically cleared when done. 1: Enable writes 0: Disable writes 1: A write operation is still going on (if WR = 1) or was prematurely terminated by a reset 0: A write operation was completed successfully.
WR
WREN
WRERR
EECON2
FIGURE 14-1 EEPROM registers The EEwrite function of Figure 14-2d likewise begins with a test to see if a write is taking place. If so, it waits for the completion of the write before proceeding. Similar to the case of the EEread function, it is better to organize program code so that only one EEPROM write occurs during each pass around the main loop. The EEwrite function then enables the brown-out reset module. The modules 34-A current draw will continue for about 10 s. The brown-out reset module will ensure that a powering-down CPU that may have inadvertently been led into code that called the EEwrite function will proceed no further. Because the three-line sequence:
EECON2 = 0x55; EECON2 = 0xAA; EECON1bits.WR = 1; // Write first key // Write second key // Set WR bit to initiate write
must be executed consecutively with no gaps in between, interrupts are suspended (if they were enabled) during the write operation. At the end of the function, both GIE and SBOREN are restored to their former state.
190
Chapter 14
EEPROM (EEtest.c)
EECON1 = 0;
EECON1bits.WREN = 0;
(b) Instruction to add to the beginning of the main loop, to disable WREN after any EEwrite during the previous loop. /**************************** * EEread * * This function reads from the EEPROM address identified by EEADR * into EEDATA. **************************** */ void EEread() { while (EECON1bits.WR); // Wait on the completion of any ongoing write EECON1bits.RD = 1; } (c) EEread function.
/**************************** * EEwrite * * This function writes the data contained * address identified by EEADR. * The write is self-timed and takes about **************************** */ void EEwrite() { while (EECON1bits.WR); // SBORENCOPY = RCONbits.SBOREN // RCONbits.SBOREN = 1; // GIEHCOPY = INTCONbits.GIEH; // INTCONbits.GIEH = 0; // EECON1bits.WREN = 1; // EECON2 = 0x55; // EECON2 = 0xAA; // EECON1bits.WR = 1; // INTCONbits.GIEH = GIEHCOPY; // RCONbits.SBOREN = SBORENCOPY; // }
Complete any ongoing write Copy SBOREN for subsequent restore Enable brown-out reset Copy GIEH for subsequent restore Disable all interrupts Enable write operation Write first key Write second key Set WR bit to initiate write Restore global interrupt state Restore brown-out reset state
Section 14.4
191
RAM
RAMPTR
EEADDRESS
EEPROM
(a) Registers
192
Chapter 14
EEPROM (EEtest.c)
char GIEHCOPY; // Copy of GIEH bit for EEPROM writes char SBORENCOPY; // Copy of SBOREN bit for EEPROM writes char* RAMPTR; // Pointer to RAM array to be copied to EEPROM char TEMPARRAY[] = {'a','b','c','d'}; // Characters to copy unsigned char EEADDRESS; // Starting address in EEPROM unsigned char EECNT; // Number of bytes to copy (b) Global variable additions. void EEread(void); void EEwrite(void); void EEArrayWrite(void); (c) Function prototype additions. EECON1 = 0; RAMPTR = TEMPARRAY + 1; EEADDRESS = 0x20; EECNT = 2; // Initialize module state // Experiment with EEPROM // Set up for writes 2 and 3 (d) Additions for Initial function. /**************************** * EEArrayWrite * * Each time this function is called with EECNT != 0, it writes one byte * from the RAM char[] pointed by RAMPTR, into the EEPROM location whose * address is in EEADDRESS. Then it increments RAMPTR and EEADDRESS and * decrements EECNT. RAMPTR, EEADDRESS and EECNT should not be modified * by other code until EECNT is 0. * Variable declaration: * char* RAMPTR; unsigned char EEADDRESS; unsigned char EECNT; * Example usage: * char TEMPARRAY[] = {'a','b','c','d'}; * RAMPTR = TEMPARRAY + 1; * EEADDRESS = 0x20; * EECNT = 2; * After 2 calls of EEArrayWrite, EECNT = 0; * and EEPROM has 'b' and 'c' in address 0x20 and 0x21 respectively. **************************** */ void EEArrayWrite() { if(EECNT && !EECON1bits.WR) { EEDATA = *(RAMPTR++); EEADR = EEADDRESS++; EEwrite(); EECNT--; } } (e) Function.
Section 14.4
193
/******* EEtest.c ************** * * Test Alex's example. * Show EEwrites by awake times on scope (using Delay(100)). * ******* Program hierarchy ***** * * main * Initial * ******************************* */ #include <p18f4321.h> /******************************* * Configuration selections ******************************* */ #pragma #pragma #pragma #pragma #pragma #pragma #pragma #pragma #pragma #pragma #pragma config config config config config config config config config config config OSC = INTIO1 PWRT = ON LVP = OFF WDT = OFF WDTPS = 4 MCLRE = ON PBADEN = DIG CCP2MX = RB3 BOR = SOFT BORV = 3 LPT1OSC = OFF // // // // // // // // // // // Use internal osc, RA6=Fosc/4, RA7=I/O Enable power-up delay Disable low-voltage programming Disable watchdog timer initially 16 millisecond WDT timeout period, nominal Enable master clear pin PORTB<4:0> = digital Connect CCP2 internally to RB3 pin Brown-out reset controlled by software Brown-out voltage set for 2.1V, nominal Deselect low-power Timer1 oscillator // Define PIC18LF4321 registers and bits
/******************************* * Global variables ******************************* */ char GIEHCOPY; // Copy of GIEH bit for EEPROM writes char SBORENCOPY; // Copy of SBOREN bit for EEPROM writes char* RAMPTR; // Pointer to RAM array to be copied to EEPROM char TEMPARRAY[] = {'a','b','c','d'}; // Characters to copy unsigned char EEADDRESS; // Starting address in EEPROM unsigned char EECNT; // Number of bytes to copy unsigned int DELAY; // Sixteen-bit counter for obtaining a delay /******************************* * Function prototypes ******************************* */ void Initial(void); void EEread(void);
194
Chapter 14
EEPROM (EEtest.c)
void EEwrite(void); void EEArrayWrite(void); /******************************* * Macros ******************************* */ #define Delay(x) DELAY = x; while(--DELAY){ Nop(); Nop(); } /////// Main program /////////////////////////////////////////////////////////// /******************************* * main ******************************* */ void main() { Initial();
// Initialize everything
// Write 1
while (1) { EECON1bits.WREN = 0; // Disable further EEPROM writes EEArrayWrite(); // Deal with EEPROM writes Sleep(); // Sleep through writes to EEPROM Nop(); } }
/******************************* * Initial * * This function performs all initializations of variables and registers. ******************************* */ void Initial() { OSCCON = 0b01100010; // Use Fosc = 4 MHz (Fcpu = 1 MHz) SSPSTAT = 0b00000000; // Set up SPI for output to LCD SSPCON1 = 0b00110000; ADCON1 = 0b00001011; // RA0,RA1,RA2,RA3 pins analog; others digital TRISA = 0b00001111; // Set I/O for PORTA TRISB = 0b01000100; // Set I/O for PORTB TRISC = 0b10000000; // Set I/O for PORTC TRISD = 0b10000000; // Set I/O for PORTD TRISE = 0b00000010; // Set I/O for PORTE PORTA = 0; // Set initial state for all outputs low
Section 14.4
195
// except RD5 that drives LCD interrupt // Pause for half a second // Now disable brown-out reset // Enable watchdog timer
// Initialize module state // Experiment with EEPROM // Set up for writes 2 and 3
/**************************** * EEArrayWrite * * Each time this function is called with EECNT != 0, it writes one byte * from the RAM char[] pointed by RAMPTR, into the EEPROM location whose * address is in EEADDRESS. Then it increments RAMPTR and EEADDRESS and * decrements EECNT. RAMPTR, EEADDRESS and EECNT should not be modified * by other code until EECNT is 0. * Variable declaration: * char* RAMPTR; unsigned char EEADDRESS; unsigned char EECNT; * Example usage: * char TEMPARRAY[] = {'a','b','c','d'}; * RAMPTR = TEMPARRAY + 1; * EEADDRESS = 0x20; * EECNT = 2; * After 2 calls of EEArrayWrite, EECNT = 0; * and EEPROM has 'b' and 'c' in address 0x20 and 0x21 respectively. **************************** */ void EEArrayWrite() { if(EECNT && !EECON1bits.WR) // Skip to next loop time for second write { EEDATA = *(RAMPTR++); EEADR = EEADDRESS++; EEwrite(); EECNT--; } } /**************************** * EEread * * This function reads from the EEPROM address identified by EEADR * into EEDATA. **************************** */
196
Chapter 14
EEPROM (EEtest.c)
// Wait on the completion of any write // Read from EEADR into EEDATA
/**************************** * EEwrite * * This function writes the data contained in EEDATA into the EEPROM * address identified by EEADR. * The write is self-timed and takes about 4 milliseconds. **************************** */ void EEwrite() { while (EECON1bits.WR); // Wait on the completion of any write SBORENCOPY = RCONbits.SBOREN; // Copy SBOREN for subsequent restore RCONbits.SBOREN = 1; // Enable brown-out reset GIEHCOPY = INTCONbits.GIEH; // Copy GIEH for subsequent restore INTCONbits.GIEH = 0; // Disable all interrupts EECON1bits.WREN = 1; // Enable write operation EECON2 = 0x55; // Write first key EECON2 = 0xAA; // Write second key EECON1bits.WR = 1; // Set WR bit to initiate write INTCONbits.GIEH = GIEHCOPY; // Restore global interrupt state RCONbits.SBOREN = SBORENCOPY; // Restore brown-out reset state PORTCbits.RC2 ^= 1; Delay(100); } (a) EEtest.c // Toggle pin to flag where write occurs // Add a delay to show sleep after 1 ms
Section 14.5
197
(d) PICkit2 verification of the three writes FIGURE 14-4 (continued) the duration of the write sequence, a powering-down of the chip that is already in progress and has reached the point where VDD is below 2 V will reset the chip. The out-of-range value of VDD may have produced a transmutation of a bit in the program counter that was following a path to the call and execution of the EEwrite function. However, when the CPU executed the brown-out reset enable instruction, the CPUs execution of further instructions was stopped dead in its tracks. Another type of concern arises when an application is subjected to a perturbation (e.g., the RF interference resulting from a lightning strike) that reaches all the way into the chip and toggles a bit of the program counter. This produces the same threat as that of the last section without any help from the solution offered there. As an example of a different approach, consider how QwikBug writes downloaded user code into the flash program memory using the WriteInitiateSequence function of Figure 14-5. The first line of the function tests a bit in an undocumented DEBUG register. When the chip is in the Background Debug Mode used by Microchips programming/debugging tools such as the PICkit 2 and also used by QwikBug, the INBUG bit is set. A perturbation of the program counter that produces an inadvertent write sequence can only be to the program memory address produced by the
EECON2 = 0x55; // Write first key
line. None other of the 8,192 addresses making up program memory will lead into the undesirable sequence. Even at that, the address pointer at that moment is pointing
198
Chapter 14
EEPROM (EEtest.c)
// Write first key // Write second key // Set WR bit to initiate write
into the user area of program memory, not into the area that holds QwikBug. In this way, the QwikBug code is protected against corruption by the inadvertent execution of its own code by a user program. A similar tack can be taken when an EEPROM write is initiated by an external event, the press of a pushbutton, for example. If the press is used to set a PRESS flag while the release is used to set a RELEASE flag, then a test of PRESS can be used to load up the registers used by the EEwrite function, and the following sequence used to initiate the write:
if (PRESS && RELEASE) { EEwrite(); PRESS = 0; RELEASE = 0; }
Section 14.5
EEPROM Life
199
For example, if only 50 bytes are used, the EEPROM memory can be divided into 256/50 blocks:
049 5099 100149 150199 200249
with six addresses left over. One of these last six (e.g., address 255) can serve as a block pointer to indicate which of the five blocks is the active block. Within each block, one address (e.g., 0 or 50 or 100 or 150 or 200) can serve as the counter of writes to the block. When the counter reaches 100,000, the 50 bytes are copied to the next block, the counter in the new block zeroed and the block pointer incremented. In this case, this scheme extends the life of the EEPROM module to (100,000/2) 10 5 = 2,500,000 erase per write cycles. This section has been a detailed look at handling the finite EEPROM life issue that never even arises for most applications. For example, the EEPROM within the microcontroller within a camera may be written to a few times during its exposure calibration at the completion of its assembly and then never written to again. In this case, the EEPROM is invaluable to the application, but its erase per write endurance is not an issue. If the MCU in the camera were a PIC18LF4321, its more relevant spec is its minimum data retention of 40 years.
PROBLEMS
14-1 SendMicroSec function a) Modify the code developed for the CalibrateX.c program of Problem 13-3 so that it stores the content of OSCTUNE into address 0xFD of EEPROM and the content of CALIB into 0xFE and 0xFF after each calibration carried out in response to a pushbutton press. b) Create a new InitOSCTUNE function. This function first looks at the char value located at 0xFF. This should hold the upper byte of CALIB if the CalibrateY.c program has been run. If CALIB = 7,812 = 0x1E84, then this upper byte will be 0x1E. Test to see if this upper byte stored in EEPROM address 0xFF falls within the range 0x1C to 0x21. If not, send the word Recalibrate to the Qwik&Low Console. If so, copy the EEPROM value from 0xFD into OSCTUNE. If this InitOSCTUNE function is called toward the end of the Initial function of a user program, that user program will be armed to produce calibrated microsecond measurements in addition to cycle count measurements.
200
Chapter 14
EEPROM (EEtest.c)
c) Create a SendMicroSec function analogous to the Send function of Figure 13-8c. Use Equation 13-1 from Section 13.3 to convert CYCLES to BIGNUM. d) Use the Delay macro to generate a 2,000 cycle pulse with Delay(200) that is immediately preceded by the call of the Start function and then the setting of RB0, and immediately followed by the clearing of RB0 and then the call of the Stop function and the SendMicroSec function. Compare the pulse width on RB0 with the result that is displayed on the PC. Now comment out just the Delay macro and do this again. The difference between these two measurements is the result of exactly 2,000 cycles of execution. How close is the resulting number of microseconds for the pulse width to the number calculated from the PC values? e) If a universal counter is available to you, use its extraordinarily accurate time interval and frequency measurement capability to check both the pulse width of RB0 and also the accuracy of FOSC/4, the CPU clock frequency, on test point TP6. Use this information to comment on the results found in Part (d).
Chapter
15
201
202
Chapter 15
Input
RD2 = 0
Output Output high = open drain out or input Output low = 0 V out
TRISD2
will pull the I/O line low. If Output is high, the MOSFET is turned off and the I/O line will be pulled high by the pull-up resistor unless some other device (the MCU in this circuit) pulls the line low. Thus, the 1-wire chip: Drives its Output low to force a 0 on the I/O line. Drives its Output high to generate an (open-drain) output of 1. Drives its Output high whenever it is expecting input data so that the MCU can control the I/O line. Draws parasitic power from the I/O line. The 1-wire protocol supports this by including at least a 180-s high interval on the I/O line before ones and zeros are transferred. This interval is sufficient to charge the internal capacitor that keeps the internal logic supplied during a message transfer. For its part, the MCU must simulate having an open-drain output with what is actually a totem-pole output; that is, an output driver with one MOSFET to pull the I/O line down to ground and another MOSFET to pull the I/O line up to VDD. It is this latter MOSFET that must never be turned on. This is achieved by treating the TRISD2 pin as the open-drain output. Thus, the MCU: Drives TRISD2 low (with RD2 low) to force a 0 on the I/O line. Drives TRISD2 high to generate an open-drain output of 1. Drives TRISD2 high whenever it is expecting input data so that the 1-wire chip can control the I/O line.
Section 15.3
203
Controlled by MCU I/O line Tlow Tslot 1 s Tlow 15 s 60 s Tslot < 120 s 1 s Trecover < (a) Writing a one. Start of bit Start of next bit Controlled by pull-up resistor Trecover
Tlow Tslot Trecover 60 s Tlow Tslot < 120 s 1 s Trecover < (b) Writing a zero.
204
Chapter 15
For clarity in his SSN.c source file, Alex Singh represents the first two of these steps with macros:
OpenDrainLow;
and
OpenDrainHigh;
and the code to send the zero shown in Figure 15-2b becomes
OpenDrainLow; Delay(6); OpenDrainHigh; // Send ZERO
Section 15.4
Tph Tpl I/O line Line idle Read ROM 64-bit SSN
Message Protocol
Trec
Line idle
Send 8-bit Read ROM command, 0x33 (least-significant bit first) Read back 64-bit Silicon Serial Number (least-significant bit first)
205
206
Chapter 15
in the DS2401 function of the SSN.c code of Figure 15-4. Each of the 8 bits is sent as discussed in the last section. In response, the DS2401 expects to be queried for its 64-bit SSN, received a bit at a time (LSb first) as discussed in the next section.
/******* SSN.c ***************** * * Read the DS2401 serial number and then sleep with RD3 high and RD2 = input * Use Fosc = 4 MHz for Fcpu = Fosc/4 = 1 MHz. * Developed by Alex Singh. * ******* Program hierarchy ***** * * main * Initial * InitTX * DS2401 * PresenceTest * SendByte * ReadByte * SaveByte * SendSSNSTRING * ******************************* */ #include <p18f4321.h> // Define PIC18LF4321 registers and bits
/******************************* * Configuration selections ******************************* */ #pragma config OSC = INTIO1 // #pragma config PWRT = ON // #pragma config LVP = OFF // #pragma config WDT = OFF // #pragma config MCLRE = ON // #pragma config PBADEN = DIG // #pragma config CCP2MX = RB3 // #pragma config BOR = SOFT // #pragma config BORV = 3 // #pragma config LPT1OSC = OFF // /******************************* * Global variables ******************************* */ unsigned char i, j; // unsigned int DELAY; // unsigned char BYTETOSEND; // unsigned char SSNBYTE; //
Use internal osc, RA6=Fosc/4, RA7=I/O Enable power-up delay Disable low-voltage programming Disable watchdog timer initially Enable master clear pin PORTB<4:0> = digital Connect CCP2 internally to RB3 pin Brown-out reset controlled by software Brown-out voltage set for 2.0V, nominal Deselect low-power Timer1 oscillator
Loop counters Sixteen-bit counter for obtaining a delay Used by SendByte Used to store byte read from DS2401
Section 15.4
Message Protocol
207
char SSNSTRING[20] = "xxxxxxxxxxxxxxxx\n\r\0"; // To hold DS2401's serial number /******************************* * Constant array in program memory ******************************* */ const char rom FormHex[] = "0123456789ABCDEF"; /******************************* * Function prototypes ******************************* */ void Initial(void); void InitTX(void); void DS2401(void); char PresenceTest(void); void SendByte(void); void ReadByte(void); void SaveByte(void); void SendSSNSTRING(void); /******************************* * Macros ******************************* */ #define Delay(x) DELAY = x; while(--DELAY){ Nop(); Nop(); } #define TXascii(in) TXREG = in; while(!TXSTAbits.TRMT) #define OpenDrainHigh TRISDbits.TRISD2 = 1 #define OpenDrainLow PORTDbits.RD2 = 0; TRISDbits.TRISD2 = 0 /////// Main program /////////////////////////////////////////////////////////// /******************************* * main ******************************* */ void main() { Initial(); // InitTX(); // DS2401(); // Sleep(); // Nop(); }
/******************************* * Initial * * This function performs all initializations of variables and registers. ******************************* */
208
Chapter 15
void Initial() { OSCCON = 0b01100010; SSPSTAT = 0b00000000; SSPCON1 = 0b00110000; ADCON1 = 0b00001011; TRISA = 0b00001111; TRISB = 0b01000100; TRISC = 0b10000000; TRISD = 0b10000100; TRISE = 0b00000010; PORTA = 0; PORTB = 0; PORTC = 0; PORTD = 0b00100000; PORTE = 0; Delay(50000); RCONbits.SBOREN = 0; }
// Use Fosc = 4 MHz (Fcpu = 1 MHz) // Set up SPI for output to LCD // // // // // // // RA0,RA1,RA2,RA3 pins analog; others digital Set I/O for PORTA Set I/O for PORTB Set I/O for PORTC Set I/O for PORTD Set I/O for PORTE Set initial state for all outputs low
// except RD5 that drives LCD interrupt // Pause for 0.5 second for contact bounce // After this delay, disable brown-out reset
/******************************* * InitTX * * This function initializes the UART for its TX output function. * It assumes Fosc = 4 MHz. ******************************* */ void InitTX() { RCSTA = 0b10010000; // Enable UART TXSTA = 0b00100000; // Enable TX SPBRG = 12; // Set baud rate BAUDCON = 0b00111000; // Invert TX output } /******************************* * DS2401 * * This function displays on the PC the serial number given * by the DS2401. If it is not present, turn on red LED and halt. ******************************* */ void DS2401() { PORTDbits.RD3 = 1; // Apply power to SSN part OpenDrainLow; Delay(50); // Master Reset with 500 us negative pulse OpenDrainHigh; if(!PresenceTest()) { PORTDbits.RD4 = 1; // Presence Test // Turn on LED
Section 15.4
Message Protocol
209
Sleep(); Nop();
//
and be done
} else { BYTETOSEND = 0x33; // Send "Read ROM" command to get back SendByte(); // serial number for (j = 16; j > 0; j-=2) // Read and save 8 bytes { ReadByte(); SaveByte(); } SendSSNSTRING(); } OpenDrainHigh; // Leave I/O pin as an input PORTDbits.RD3 = 1; // Leave SSN part empowered } /******************************* * PresenceTest * * This function returns a 1 if DS2401 is present and a 0 otherwise. ******************************* */ char PresenceTest() { Delay(10); // After 100 us if (!PORTDbits.RD2) // check for Presence { while (!PORTDbits.RD2); // Wait for line to be released return 1; } return 0; } /******************************* * SendByte * * This function sends BYTETOSEND to DS2401. Tslot = 85 us for ONE or for ZERO. ******************************* */ void SendByte() { for (i = 0; i <= 7; i++) // Send 8 bits of command { if (BYTETOSEND & 1) // Test bit 0 { OpenDrainLow; OpenDrainHigh; // Send ONE Delay(6); }
210
Chapter 15
// Send ZERO
/******************************* * ReadByte * * This function reads in the 64-bit serial number a byte at a time. * Tslot = 70 us for ONE or for ZERO. * DS2401 holds line low for zero for 35 us. ******************************* */ void ReadByte() { SSNBYTE = 0; // Initialize SSNBYTE for (i = 0; i <= 7; i++) // Read 8 bits from DS2401 { SSNBYTE >>= 1; // Move on to next bit OpenDrainLow; OpenDrainHigh; Delay(1); // Sample 13 us after 'clocking' if (PORTDbits.RD2) { SSNBYTE += 0b10000000; // Copy bit into SSNBYTE } Delay(3); } } /******************************* * SaveByte * * This function converts the binary value in SSNBYTE into two * ASCII-coded digits in SSNSTRING. ******************************* */ void SaveByte() { SSNSTRING[j - 2] = FormHex[SSNBYTE >> 4]; // Save upper nibble SSNSTRING[j - 1] = FormHex[SSNBYTE & 0x0F]; // Save lower nibble }
Section 15.6
211
/******************************* * SendSSNSTRING * * This function sends SSNSTRING to the PC. ******************************* */ void SendSSNSTRING() { for (i=0; SSNSTRING[i]; ++i) { TXascii(SSNSTRING[i]); // Send all bytes until null terminator } }
212
Chapter 15
Start of bit Start of next bit Controlled by pull-up resistor Controlled by MCU Controlled by DS2401 I/O line Tlow Tread Tslot 1 s Tlow < 15 s Tread = 15 s 60 s Tslot < 120 s (a) Reading a one Start of bit Start of next bit Controlled by pull-up resistor Controlled by MCU Controlled by DS2401 I/O line Tlow Tread Trelease
Section 15.7
213
More generally, 1-wire device interactions can be employed during the on-going operation of a larger program, to take advantage of the versatility of 1-wire devices. During such interactions, it is important to disable interrupts before each bit transfer is initiated. Nothing is lost if an interrupt service routine intervenes after the MCU releases the I/O line and has, in addition, completed the reading or the writing of a bit. While the I/O line is high, an I/O message string can be paused indefinitely. However, if an interrupt occurs while the MCU is holding the line low and the resulting pause causes a 1-wire device to be held low for more than 120 s, the serial I/O messaging protocol may be reset.
214
Chapter 15
First read 1 0 0
Second read 0 1 0
Interpretation All devices have a 1 in this ROM address position All devices have a 0 in this ROM address position Devices differ in this ROM address position (a) Interpretation of a pair of reads.
Write 1 0
Interpretation Only devices with a 1 in this ROM address position remain enabled Only devices with a 0 in this ROM address position remain enabled (b) Meaning of the write during the next Time slot. Bit number 0 1 1 0 2 1 3 1 4 0
ROM address of selected part Read-read-write sequence Select both devices Mismatch detected between parts Select one device Keep selecting the selected part to obtain its complete ROM address
1 0 1 0 1 0 0 0 1 1 0 1 0 1 0
(c) Example, obtaining the ROM address of one of a pair of 1-wire parts.
time slot (Figure 15-6b) is used as a write, to select all devices that match the bit written to them. Figure 15-6c illustrates the Search ROM process of successive read-read-write bits to sort out the ROM address of one of the devices. If a 1 is written during the third of the three time slots each time a bit mismatch is detected (by a read-read of 0-0), the result will be to select the part with the highest ROM address. If only two 1-wire parts are connected to the 1-wire bus, the first bit mismatch found will be the only bit mismatch needed to obtain the second parts ROM address. Instead of writing a one back for this bit, a zero is written, deselecting the chip whose
Section 15.8
215
1 1 0 0 1 0
0 1
1 1
0 1
1 0 1
0 1 1 0 1 9
1 0 1 1 0 0 Bit position 0 1 2 3 4 5 6
1 0 1 7 1 8
ROM address has already been found in order to continue to determine the ROM address for the second part. For three 1-wire parts connected to the 1-wire bus, the path to select one of the first two chips will include a second mismatch. If the part having a one in this second mismatch position is taken, then subsequently the path having a zero in this second mismatch position must be followed. For more than three 1-wire parts connected to the 1-wire bus, the determination of each ROM address amounts to tracing out each branch of a tree having 64 bits in it, as illustrated in Figure 15-7. Dallas/Maxim addresses this determination systematically in an application note that can be found by Googling Book of iButton Standards and then finding on PDF page 58, Section C.3 Search ROM Command: The general principle of this search process is to deselect one device after another at every conflicting bit position. At the end of each ROM Search process, the master has learned another ROMs contents. The next pass is the same as the previous pass up to the point of the last decision. At this point, the master goes in the opposite direction and continues. If another conflict is found, again zero is written, and so on. After both paths at the highest conflicting bit position are followed to the end, the master goes the same way as before but deciding oppositely at a lower conflicting position, and so on, until all ROM data are identified.
216
Chapter 15
6 32768 Hz crystal 5
X2 X1
VDD GND
3 1 0.1 F
GND
and RA4 selected from Figure 3-7) can be used. Although it would seem that the pull-up resistor could be powered directly from the coin cell, unfortunately when the power switch is opened, the pull-up resistor voltage will exceed the MCUs VDD voltage of 0 V and will lead to a constant current drain of 3 V/5 k = 600 A. The DS2415 time chip has the following features: It draws a maximum of 0.25 A with its oscillator incrementing an internal settable and readable 32-bit counter every second. By being continuously powered, its counter can be employed as a real-time clock, accumulating 136 years of seconds before rolling over. With just one 1-wire device on the bus of Figure 15-8, the message protocol of Figure 15-9 begins with the Reset pulse/Presence pulse sequence. This is followed by the Skip ROM (0xCC) command so that all subsequent I/O interactions will be responded to by that chip. Once selected by the Skip ROM command, the DS2415 responds to just the two commands shown in Figure 15-9: Write Clock Read Clock (0x99) (0x66)
Section 15.8
217
Line idle
Reset pulse
Counter MSB
: Start oscillator : Stop oscillator
Reset pulse
Line idle
(a) Write sequence Line idle Reset pulse Presence Skip pulse ROM Read Control clock byte LSB Counter MSB
: Oscillator is running : Oscillator is stopped
Line idle
FIGURE 15-9 DS2415 write and read message sequences. (Each byte is sent, or received, least significant bit first) Thus, to control the oscillator and set the counter, the write sequence of Figure 15-9a produces the following actions: The reception of the control byte immediately starts (0x0C) or stops (0x00) the DS2415 oscillator. The 4 bytes destined for the counter are received least-significant-byte first and are loaded into a buffer, not the counter itself. The final reset pulse transfers the buffer to the counter. The read sequence of Figure 15-9b is responded to as follows: When the Read Clock command has been received, the 32-bit counter is copied into the buffer. The next 5 bytes received are the control byte followed by the 4-byte counter content.
PROBLEMS
15-1 Interrupts and 1-wire devices As pointed out in Section 15.6, interrupts can occur during a 1-wire transfer without corrupting the transfer if interrupts are disabled during each bit transfer and enabled momentarily between successive bit transfers. Add repeated reads of the DS2401 every second to the T3.c template file that uses both Timer1 and Timer3 for interrupts. a) Execute your resulting code. Does the serial number, repeatedly read, ever get corrupted? Explain.
218
Chapter 15
b) Add Delay (50) to your HighPriorityISR so that you are introducing a 500-s pause every 4 ms. Does the serial number, repeatedly read, ever get corrupted? Explain. c) Modify the SendByte and ReadByte functions in the code for Part (b) with
PIE2bits.TMR3IE = 0; // Disable Timer3 interrupts
and
PIE2bits.TMR3IE = 1; // and quickly reenable
lines appropriately placed, as suggested in Section 15.6. Now does the serial number, repeatedly read, ever get corrupted? If so, add one or two Nop macros between an enable and a following disable, to ensure the momentary enabling of the Timer3 interrupts. Explain what you find including any need for N intervening Nop macros. 15-2 Search ROM command Use the Search ROM command of Section 15.7 to keep the DS2401 chip enabled for the first 55 bits of its serial number. As each bit is determined, send it back to the PC for display, as an ASCII-coded one or zero. Send a space character (0x20) after every group of eight characters sent to the PC. For the 56th bit, send the complement of the found bit both to the PC and the DS2401. Thereafter, again send each of the remaining 8 bits read via the Search ROM 3-bit-time procedure back to the PC for display. Compare what you get relative to what the SSN.c program produces. Explain the result. 15-3 DS2415 time chip Assume that the DS2415 was cleared to zero and started counting many days ago. a) Write a function, Days, that will take the number read back into the unsigned long variable, TIME, and convert it to an LCD display of days, with a resolution of 0.01 day and a range up to 9,999.99 days.
TIME = (60602451) + (606024.16) = 4406400 + 13824 = 4420224 seconds
b) Write a second function, DeltaDays, that displays, in the same format, the difference
TIMEEND TIMESTART
Chapter
16
220
Chapter 16
the backplane segment. Its RMS value must be above a threshold specific to the liquidcrystal medium. Correspondingly, to turn off a segment, the RMS value between the frontplane segment and the backplane segment must be below a specified threshold. The job of controlling LCD segments is simplified for a nonmultiplexed display such as a three-digit, seven-segment numeric display. Its 7 3 = 21 digit segments, 2 decimal points, and a single backplane driver are brought out to 24 pins. A lowfrequency squarewave between VDD and GND is applied to the entire backplane. An off segment has the same waveform applied to its frontplane (for an RMS value of 0 V across the liquid-crystal medium for that segment). An on segment has the inverted squarewave applied to its front plane (for an RMS value of VDD volts). Thus, for VDD = 3 V, the liquid-crystal medium must only distinguish between 0 V and 3 V. The result is a display with a sharp distinction between on and off segments, regardless of the viewing angle. In contrast, as will be discussed shortly, the multiplexing used by the Qwik&Low LCD is supported by complex waveforms for which each period is made up of stairstep values of 0 V, 1 V, 2 V, and 3 V. The liquid-crystal medium must draw a distinction between an RMS on voltage of 1.73 V and an RMS off voltage of 1.00 V. The result is a display with a strong sensitivity to viewing angle. The Qwik&Low boards LCD is designed to be viewed from the 6:00 oclock position that results when the board sits flat on the workstation in front of a user. The characters fade as the viewing angle approaches the straight on position or is moved to either side. This is a drawback of using a display with only 36 pins and an LCD controller chip designed to drive it while drawing a miniscule current of just a few microamperes. Returning to Figure 16-2b, each frontplane SEGi pin is connected to four segments. For example, the SEG0 pin is connected to segments A, B, C, and P (the decimal point) for the character in POSITION 0 (the left-most character position of the display). SEG4 is connected to segments A, B, C and P for the character in POSITION 1. Each backplane COMj pin is connected, on the back of the display, to just one of the four segments associated with each frontplane SEGi pin. Thus, it is a controlling factor for 8 (characters) 4 (segment pins per character) = 32 segments
Section 16.3
221
SEG2
SEG0
SEG6
SEG4
X H
I J K
I J
L M N
L M N
SEG1
SEG3
SEG5
SEG7 Position 1
Position 0 (left-most character) (a) Segment coding SEG0 SEG2 SEG1 SEG3
A B C P
X F E D
H G L M
I J K N
A B C P
X F E D
H G L M
I J K N
Position 0
Position 1
(b) Connections for frontplane SEG drivers and backplane COM drivers
222
Chapter 16
VDD (3 V)
COM0 COM1
COM2 COM3
58 55 54 53 52 51
35 1 36 2 33 3
RD5 INT0 TP9 SCK SCK SCK TP8 SDI SDO SDI TP7 6390 RC7 RC7 SEG31 SEG3 SEG4 SEG5
59
16
SEG31
FIGURE 16-3 LCD controller circuit (simplified) to the LCD controller or, alternatively, it breaks these connections to power down the LCD. The latter condition is used to clarify the current draw by Qwik&Low circuitry independent of the LCD current draw. It is also used to measure the small increase in current draw when the LCD current is added into that drawn for a Qwik&Low application. The three pins that control the use of the Serial Peripheral Interface (SPI) for updating the display from the MCU are also disconnected from the MCU when the LCD controller is powered down. If this were not done, any MCU operation that left any of these outputs high would produce a leakage current, measured in milliamperes, through
Section 16.4
223
the overvoltage protection diodes on the LCD controller inputs to the grounded VDD pin on the LCD controller. In addition to the three test points for monitoring Serial Peripheral Interface transfers, the LCD controller offers two other test points. TP11 (FOSC/4) is used to monitor how long it takes the LCD controller to respond to new SPI data and to format the characters received for display into the chips LCD registers before going back to sleep, the normal state of the LCD controllers CPU. Another pin, labeled on the back of the Qwik&Low board as TP7 (6390 RC7), provides a user-defined flag. It can be used to monitor conditions or measure time intervals introduced if modifications are made to the LCD.c file employed by the LCD controller.
224
Chapter 16
One frame 3V Backplane 2V COM0 driver selects first quarter frame 1 V 0V One frame 3V Backplane 2V COM1 driver selects second quarter frame 1 V 0V One frame 3V Backplane 2V COM2 driver selects third quarter frame 1 V 0V One frame 3V Backplane 2V COM3 driver selects fourth quarter frame 1 V 0V
Section 16.6
225
3V 2V 1V (c) SEGi COM0 0V 1 V 2 V 3 V (d) (SEGi COM0)2 (e) Mean of (SEGi COM0)2 (f) RMS value = = = 1 V2 (1 1 V2 1 V2 + 1 V2 1 1 V2 1 V2 + 1 V2 1 1 V2
+ 1
+ 1
+ 1
+ 1
+ 1)/8 = 1 V2
1 V2 = 1.00 V
FIGURE 16-5 All four segments selected by SEGi are off LCDDATA0 and LCDDATA6 and LCDDATA12 and LCDDATA18 The allocation of the segments for all eight characters is shown in Figure 16-7.
226
Chapter 16
3V 2V 1V (c) SEGi COM0 0V 1 V 2 V 3 V (d) (SEGi COM0)2 (e) Mean of (SEGi COM0)2 (f) RMS value = = = 9 V2 (9 9 V2 1 V2 + 1 V2 1 1 V2 1 V2 + 1 V2 1 1 V2
+ 9
+ 1
+ 1
+ 1
+ 1)/8 = 3 V2
3 V2 = 1.73 V
FIGURE 16-6 RMS voltage across a selected turned-on segment offset into the table of 256 2-byte entries shown in Figure 16-8. As shown by the comment at the beginning of this table, each table entry consists of the 4 nibbles
JGFB IHXA NMDP KLEC
packed into the 2 bytes of an unsigned int constant stored in program memory. The LCD.c file of Figure 16-13 will unpack the nibbles for each character to be displayed and load them into the appropriate LCDDATAi registers.
Section 16.6
227
POSITION
LCD Pin
Segment
Segment
Segment
Segment
35 SEG0 0 1 SEG1 36 SEG2 2 SEG3 33 SEG4 1 3 SEG5 34 SEG6 4 SEG7 31 SEG8 2 5 SEG9 32 SEG10 6 SEG11 29 SEG12 3 7 SEG13 30 SEG14 8 SEG15 27 SEG16 4 9 SEG17 28 SEG18 10 SEG19 25 SEG20 5 11 SEG21 26 SEG22 12 SEG23 23 SEG24 6 13 SEG25 24 SEG26 14 SEG27 21 SEG28 7 15 SEG29 22 SEG30 16 SEG31
LCDDATA0,0 LCDDATA0,1 LCDDATA0,2 LCDDATA0,3 LCDDATA0,4 LCDDATA0,5 LCDDATA0,6 LCDDATA0,7 LCDDATA1,0 LCDDATA1,1 LCDDATA1,2 LCDDATA1,3 LCDDATA1,4 LCDDATA1,5 LCDDATA1,6 LCDDATA1,7 LCDDATA2,0 LCDDATA2,1 LCDDATA2,2 LCDDATA2,3 LCDDATA2,4 LCDDATA2,5 LCDDATA2,6 LCDDATA2,7 LCDDATA3,0 LCDDATA3,1 LCDDATA3,2 LCDDATA3,3 LCDDATA3,4 LCDDATA3,5 LCDDATA3,6 LCDDATA3,7
A X H I A X H I A X H I A X H I A X H I A X H I A X H I A X H I
LCDDATA6,0 LCDDATA6,1 LCDDATA6,2 LCDDATA6,3 LCDDATA6,4 LCDDATA6,5 LCDDATA6,6 LCDDATA6,7 LCDDATA7,0 LCDDATA7,1 LCDDATA7,2 LCDDATA7,3 LCDDATA7,4 LCDDATA7,5 LCDDATA7,6 LCDDATA7,7 LCDDATA8,0 LCDDATA8,1 LCDDATA8,2 LCDDATA8,3 LCDDATA8,4 LCDDATA8,5 LCDDATA8,6 LCDDATA8,7 LCDDATA9,0 LCDDATA9,1 LCDDATA9,2 LCDDATA9,3 LCDDATA9,4 LCDDATA9,5 LCDDATA9,6 LCDDATA9,7
B F G J B F G J B F G J B F G J B F G J B F G J B F G J B F G J
LCDDATA12,0 LCDDATA12,1 LCDDATA12,2 LCDDATA12,3 LCDDATA12,4 LCDDATA12,5 LCDDATA12,6 LCDDATA12,7 LCDDATA13,0 LCDDATA13,1 LCDDATA13,2 LCDDATA13,3 LCDDATA13,4 LCDDATA13,5 LCDDATA13,6 LCDDATA13,7 LCDDATA14,0 LCDDATA14,1 LCDDATA14,2 LCDDATA14,3 LCDDATA14,4 LCDDATA14,5 LCDDATA14,6 LCDDATA14,7 LCDDATA15,0 LCDDATA15,1 LCDDATA15,2 LCDDATA15,3 LCDDATA15,4 LCDDATA15,5 LCDDATA15,6 LCDDATA15,7
C E L K C E L K C E L K C E L K C E L K C E L K C E L K C E L K
LCDDATA18,0 LCDDATA18,1 LCDDATA18,2 LCDDATA18,3 LCDDATA18,4 LCDDATA18,5 LCDDATA18,6 LCDDATA18,7 LCDDATA19,0 LCDDATA19,1 LCDDATA19,2 LCDDATA19,3 LCDDATA19,4 LCDDATA19,5 LCDDATA19,6 LCDDATA19,7 LCDDATA20,0 LCDDATA20,1 LCDDATA20,2 LCDDATA20,3 LCDDATA20,4 LCDDATA20,5 LCDDATA20,6 LCDDATA20,7 LCDDATA21,0 LCDDATA21,1 LCDDATA21,2 LCDDATA21,3 LCDDATA21,4 LCDDATA21,5 LCDDATA21,6 LCDDATA21,7
Segment P D M N P D M N P D M N P D M N P D M N P D M N P D M N P D M N
228
Chapter 16
// Table-entry coding of segments: const rom unsigned int ASCII[] = { // ASCII column 0 0x0100, 0x1000, 0x0001, 0x0020, 0x0002, 0x2000, 0x4000, 0x0400, 0x0800, 0x8000, 0x0008, 0x0080, 0x0040, 0x0004, 0x0200, 0x0010, // ASCII column 0xffff, 0xcedc, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // ASCII column 0x0000, 0xffff, 0xffff, 0xffff, 0x8080, 0x0404, 0x0000, 0x4008, // ASCII column 0xb127, 0x1001, 0x7009, 0x6129, 0x712b, 0x7129, 0x8080, 0x4028, // ASCII column 0xffff, 0x710b, 0x1961, 0x6122, 0x700b, 0x0960, 0x2022, 0xb403, // ASCII column 0x710a, 0x31a3, 0x0940, 0x3023, 0x8484, 0x8440, 0xffff, 0xffff, // ASCII column 0xffff, 0x710b, 0x1961, 0x6122, 0x700b, 0x0960, 0x2022, 0xb403, // ASCII column 0x710a, 0x31a3, 0x0940, 0x3023, 0x8484, 0x8440, 0xffff, 0xffff, }; 1 0xffff, 0xffff, 0xffff, 0xffff, 2 0xffff, 0xffff, 0xc48c, 0xb137, 3 0x512a, 0x612b, 0xb137, 0x0404, 4 0x1969, 0x6102, 0x1023, 0x3483, 5 0x718a, 0xa006, 0x8124, 0x0084, 6 0x1969, 0x6102, 0x1023, 0x3483, 7 0x718a, 0xa006, 0x8124, 0xffff,
J G F B
I H X A
N M D P
K L E C
// Chris Bruhn's and Peter Ralston's // modification for PV.c, their // performance verification program
// CB and PR again
// // // //
// // // //
// // // //
Section 16.8
229
230
Chapter 16
not sent. Rather than wait indefinitely with the (FOSC = 8 MHz) clock drawing its relatively heavy current, the CPU stops waiting, displays what it has received, and returns to sleep.
The data structure is set up so that a pointer can be used to identify the lowest of the four addresses. From Figure 16-7, it can be seen that the lowest address for POSITION 0 and POSITION 1 is the address of LCDDATA0. The lowest address for POSITION 2 and POSITION 3 is the address of LCDDATA1, etc. Then, depending on whether POSITION is even or odd, the RAM nibbles will be written to the lower nibble or upper nibble of the selected register.
231
LCDDATA ALL = ASCII [CHAR]; // Load table entry to be written (a) Reading from table into data structure JGFB LCDDATA P (decimal point bit) LCDDATA nHH IHXA NMDP KLEC
LCDDATA ALL (b) Desired members within the data structure, LCDDATA union { struct { unsigned int ALL; }; struct { unsigned nLL:4; unsigned nLH:4; unsigned nHL:4; unsigned nHH:4; }; struct { unsigned:4; unsigned P:1; }; } LCDDATA;
// To identify the decimal point bit // structure to handle table entries as nibbles
(c) Data structure definition to assign members to parts of ASCII table entry.
FIGURE 16-10 ASCII table entry members Figure 16-11 lists the actual addresses of the LCDDATAi registers. The data structures of Figure 16-12 assign members that will be written to, once the pointer has been set to point to the first address (e.g., the address of LCDDATA1 for Character Number 2). As the DisplayV function reads an ASCII code from VSTRING into CHAR, it also forms POSITION, the character location destined to display the character. WriteCharacter, in turn, forms the pointer:
ptr = (oneLCDSEG *) (&LCDDATA0 + POSITION / 2);
232
Chapter 16
Hex address F60 F61 F62 F63 F64 F65 F66 F67 F68 F69 F6A F6B F6C F6D F6E F6F F70 F71 F72 F73 F74 F75 F76 F77 F78 F79 F7A
+5
+10
+5
(a) This structure will be used below to add an offset of five bytes to an address in the oneLCDSEG data structure
// LCDDATA(x)
// LCDDATA(x+6)
233
unsigned nBH:4; _5bytes b; _5bytes c; unsigned nCL:4; unsigned nCH:4; _5bytes d; unsigned nDL:4; unsigned nDH:4; } oneLCDSEG;
// LCDDATA(x+12)
// LCDDATA(x+18)
that it uses to write each nibble from the LCDDATA data structure to the nibble of a register with a line exemplified by:
ptrnAL = LCDDATA.nHL;
234
Chapter 16
#include <p18f6390.h> /******************************* * Assembler directives ******************************* */ #pragma #pragma #pragma #pragma #pragma #pragma #pragma config config config config config config config OSC = INTIO7 WDT = OFF PWRT = ON MCLRE = ON XINST = OFF BOREN = ON BORV = 3 // // // // // // // Internal osc, RA6=CLKO, RA7=I/O WDT disabled (control through SWDTEN bit) PWRT enabled MCLR pin enabled; RG5 input pin disabled Instruction set extension disabled Brown-out controlled by software Brown-out voltage set for 2.0V, nominal
/******************************* * Structure definitions ******************************* */ // Structures to map LCDDATA (as nibbles) typedef struct { long _4bytes; char _1byte; } _5bytes; typedef struct { unsigned nAL:4; unsigned nAH:4; _5bytes a; unsigned nBL:4; unsigned nBH:4; _5bytes b; _5bytes c; unsigned nCL:4; unsigned nCH:4; _5bytes d; unsigned nDL:4; unsigned nDH:4; } oneLCDSEG; /******************************* * Global variables ******************************* */ union { struct
// LCDDATA(x)
// LCDDATA(x+6)
// LCDDATA(x+12)
// LCDDATA(x+18)
235
{ unsigned int ALL; }; struct { unsigned nLL:4; unsigned nLH:4; unsigned nHL:4; unsigned nHH:4; }; struct { unsigned:4; unsigned P:1; }; } LCDDATA; // To identify entire structure
// To identify the decimal point bit // Structure to handle table entries as // nibbles // // // // // // // // // // Flag to handle a received decimal point ASCII character from string LCD character position (0 to 7) Used as index for loops and VSTRING Sixteen-bit counter for obtaining a delay Used for delay in Initial Used to keep track of characters received Pointer that maps to our LCD nibbles Pointer used to clear all LCDDATA Variable string to display
char DPFLAG; unsigned char CHAR; unsigned char POSITION; unsigned char i; unsigned int DELAY; unsigned int j; unsigned int RECEIVED; oneLCDSEG *ptr; char *CLEARptr; unsigned char VSTRING[10]; /******************************* * Constant strings ******************************* */
// Table-entry coding of segments: const rom unsigned int ASCII[] = { // ASCII column 0x0100, 0x1000, 0x0002, 0x2000, 0x0800, 0x8000, 0x0040, 0x0004, // ASCII column 0xffff, 0xcedc, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0 0x0001, 0x4000, 0x0008, 0x0200, 1 0xffff, 0xffff, 0xffff, 0xffff,
J G F B
I H X A
N M D P
K L E C
// Chris Bruhn's and Peter Ralston's // modification for PV.c, their // performance verification program
// CB and PR again
236
Chapter 16
// ASCII column 0x0000, 0xffff, 0xffff, 0xffff, 0x8080, 0x0404, 0x0000, 0x4008, // ASCII column 0xb127, 0x1001, 0x7009, 0x6129, 0x712b, 0x7129, 0x8080, 0x4028, // ASCII column 0xffff, 0x710b, 0x1961, 0x6122, 0x700b, 0x0960, 0x2022, 0xb403, // ASCII column 0x710a, 0x31a3, 0x0940, 0x3023, 0x8484, 0x8440, 0xffff, 0xffff, // ASCII column 0xffff, 0x710b, 0x1961, 0x6122, 0x700b, 0x0960, 0x2022, 0xb403, // ASCII column 0x710a, 0x31a3, 0x0940, 0x3023, 0x8484, 0x8440, 0xffff, 0xffff, };
2 0xffff, 0xffff, 0xc48c, 0xb137, 3 0x512a, 0x612b, 0xb137, 0x0404, 4 0x1969, 0x6102, 0x1023, 0x3483, 5 0x718a, 0xa006, 0x8124, 0x0084, 6 0x1969, 0x6102, 0x1023, 0x3483, 7 0x718a, 0xa006, 0x8124, 0xffff,
// // // //
// // // //
// // // //
// // // //
// // // //
// // // //
/******************************* * Variable strings ******************************* */ /******************************* * Function prototypes ******************************* */ void Initial(void); void DisplayV(void); void WriteCharacter(void);
237
/******************************* * Macros ******************************* */ #define Delay(x) DELAY = x; while(--DELAY){ Nop(); Nop(); } /******************************* * main() ******************************* */ void main() { Initial(); // Initialize everything while (1) { Sleep(); Nop(); CHAR = SSPBUF; // Clear buffer initially INTCONbits.INT0IF = 0; // Clear wake up flag TMR0L = 0; // Reset breakout timer INTCONbits.TMR0IF = 0; // Clear breakout timer flag for (RECEIVED = 0; RECEIVED < 9; RECEIVED++) // Receive 9 chars { PIR1bits.SSPIF = 0; // Clear SPI flag // Wait limited amount of time for character while ((!PIR1bits.SSPIF) && (!INTCONbits.TMR0IF)) ; if (INTCONbits.TMR0IF) { break; } VSTRING[RECEIVED] = SSPBUF; // Get character and put into string } DisplayV(); } } /******************************* * Initial() * * This subroutine performs all initializations of variables and registers. ******************************* */ void Initial() { OSCCON = 0b01110010; LCDSE0 = 0b11111111; LCDSE1 = 0b11111111; LCDSE2 = 0b11111111; LCDSE3 = 0b11111111; LCDCON = 0b10001011;
238
Chapter 16
LCDPS = 0b00110110; // CLEARptr = (char *) &LCDDATA0; for (i = 0; i < 28; i++) // { *CLEARptr++ = 0x00; } LCDDATA21 = 0b00010000; // ADCON1 = 0b00111111; // TRISA = 0; // TRISB = 0b00000001; TRISC = 0b00011000; PORTA = 0; PORTB = 0; PORTC = 0; SSPCON1 = 0b00110101; // SSPSTAT = 0b00000000; T0CON = 0b11000011; // INTCON2bits.INTEDG0 = 0; // INTCONbits.INT0IF = 0; // INTCONbits.INT0IE = 1; // // Don't enable interrupt, only DPFLAG = 0; Delay(30000); // RCONbits.SBOREN = 0; // }
Turn on rightmost decimal point initially Make all ADC/IO pins digital Make all pins outputs but RB0, SCK, SDI
Initialize SPI as slave Use Timer0 to timeout on incomplete input Wake up with falling edge on INT0 Clear flag Enable INT0 source wakeup Initial delay is 300/2 milliseconds Now disable brownout reset
/******************************* * DisplayV() * * This subroutine displays the string stored in VSTRING ******************************* */ void DisplayV() { // Iterate through all received for (i = 0, POSITION = 0; i < RECEIVED; i++, POSITION++) { CHAR = VSTRING[i]; // Save byte if (VSTRING[i + 1] == '.') // Deal with decimal point { // Check next character for decimal point DPFLAG = 1; // If it is, set flag i++; // and increment pointer past decimal point } WriteCharacter(); // Display current character } }
239
/******************************* * WriteCharacter() * * This subroutine writes the selected character to the display ******************************* */ void WriteCharacter() { LCDDATA.ALL = ASCII[CHAR];
// Load table entry to be written // Ppoint to corresponding LCDDATAs ptr = (oneLCDSEG *) (&LCDDATA0 + POSITION / 2); if (DPFLAG) // if FLAG is set { LCDDATA.P = 1; // Write decimal point DPFLAG = 0; // and clear flag } if (!(POSITION % 2)) // If even position { ptr->nAL = LCDDATA.nHL; // write to lower nibbles ptr->nBL = LCDDATA.nHH; ptr->nCL = LCDDATA.nLL; ptr->nDL = LCDDATA.nLH; } else // If odd position { ptr->nAH = LCDDATA.nHL; // write to upper nibbles ptr->nBH = LCDDATA.nHH; ptr->nCH = LCDDATA.nLL; ptr->nDH = LCDDATA.nLH; }
PROBLEMS
16-1 Algorithm testing This problem requires the availability of a PICkit 2 programmer plus a 6-pin, 100-mil-spaced header to bridge between the programmers 6-pin female output and the Qwik&Low boards unpopulated H5 LCD PICkit 2 header. Modify the LCD.c code to set RC7 before the call of WriteCharacter in DisplayV and to clear RC7 on the return from WriteCharacter. Compile your modified LCD.c code with the same c18.exe utility used to compile the code for the PIC18LF4321. Note the message produced by this utility indicating its recognition (and acceptance) of this code for the PIC18LF6390 LCD controller chip.
240
Chapter 16
Use the PICkit 2 programming utility (rather than QwikProgram used to program QwikBug into the PIC18LF4321) to program the LCD controller chip. When you do this, make sure that SW2, the big 4PDT switch in the center of the board, is down or in (i.e., on, not off). Otherwise, this switch shorts the LCD controllers VDD pin to ground, as can be seen in Figure 16-3. Monitor test point TP7 with a scope while running MCU code that writes to the LCD display. This test point is labeled on the back of the board below TP8, TP9, TP10, and TP11. It can, or course, be probed from the front of the board at the pad located below the TP8 label. a) Does the duration of WriteCharacter vary with the position of the character on the display? To test this, have the MCU send AAAAAAAAA to the display. What is this duration? b) Does the duration of WriteCharacter vary with the character code? To test this, you already know the effect of the position of the character. Now have the MCU send AOPZ.aopz to the display. What do you find?
Chapter
17
242
Chapter 17
Many peripheral functions available with an I2C interface are also available with an SPI interface. For example, the AD5601 digital-to-analog (DAC) considered later in this chapter is the SPI counterpart of the AD5602, a DAC with an I2C interface and an otherwise identical feature set.
SPI Control circuitry SSPIF flag PIR1 SSPBUF Internal shift register SCK (RC3) Serial clock (eight clock pulses are emitted in response to a write to SSPBUF)
SDO (RC5)
(a) Function of pins CS Clear flag SSPIF Write to SSPBUF SCK SDO b7 b6 b5 b4 b3 b2 b1 b0 Flag set upon completion of transfer
8 s (b) Waveforms
Section 17.2
243
7 6 5 4 3 2 1 0 0 x x x TRISC x x 0 SCK = output SDO = output SDI/RC4 7 6 5 4 3 2 1 0 SSPCON1 0 0 1 0 0 0 0 SPI clock rate = FOSC/4 CKP - see Figure 17-3 SSPEN = 1 to enable SPI module Unused in SPI mode 7 6 5 4 3 2 1 0 0 0 0 0 0 0 Unused in SPI mode CKE - see Figure 17-3 SMP - used by SPI inputs, see Figure 17-5 7 6 5 4 3 2 1 0 PIR1 SSPIF SSPBUF 1: Transfer completed (must be cleared before transfer) 0: SPI ready to transfer 1: for use by SPI for serial input transfers 0: for use as a general-purpose output
SSPSTAT
FIGURE 17-2 SPI registers The registers associated with the SPI module are shown in Figure 17-2. If the SPI is only being used for output transfers (as it is for the Qwik&Low board without addon parts), then the initialization of bit 4 of TRISC to zero (as it is initialized in all of the earlier template files) allows the SDI pin to be used as a general-purpose output pin, RC4. The SPI clock rate is controlled by the lower 4 bits of SSPCON1. The 0000 choice shown sets this rate to its maximum value of 1 MHz when FOSC = 4 MHz so that an 8-bit transfer will take just 8 s. Two control bits, CKP and CKE, serve a vital role for SPI output transfers. CKP selects whether the clock output, SCK, idles high or low. These two control bits are initialized before initiating an SPI transfer to a device. If the device requires each incoming bit to be stable at the time of the rising edge of its SCK input, then either Figure 17-3a or c will serve. On the other hand, if the Figure 17-3b or d choice were made, that device would see a changing, ambiguous bit on SDO at the time of each rising edge of SCK. For a device that requires stable data with the rising edge of the clock, either Figure 17-3a or c will work. For a device that requires stable data with the falling edge of the clock, either Figure 17-b or d will work. Between the two choices for a device, the better choice is the one that does not change CKP from its previous value. Changing
244
Chapter 17
SCK SDO b7 b6 b5 b4 b3 b2 b1 b0
(a) CKP = 1, CKE = 0 (selection for transfers to LCD controller) SCK SDO b7 b6 b5 b4 b3 b2 b1 b0
FIGURE 17-3 CKP and CKE options for output transfers CKP introduces two extra edges, one of which will clock falling-edge-sensitive devices and one of which will clock rising-edge-sensitive devices. For example, as indicated by Figure 17-3a, the LCD controller is sensitive to rising clock edges. If CKP is changed to zero for an SPI transfer to another device, that change in CKP will drop the idle SCK line from high to low. That change may clock the other device and produce an inadvertent 9-bit transfer to the device. Likewise, when CKP is changed back to one for the next SPI transfer to the LCD controller, an extra rising edge will occur on the SCK input to the LCD controller. If the LCD controllers CPU has not yet been awakened by the interrupt input from the MCU (see Figure 5-1a), then this extra edge will be ignored. On the other hand, less careful dealing with this sequence by first dropping RD5 to wake up the LCD controllers CPU and then changing CKP will clock a garbage bit into the LCD controller and thereby garble what is received. This potential problem never arises if CKP is never changed. SPI output transfers to a device generally involve an extra control line in addition to the SCK to SCK connection and the SDO to SDI connection (see Figure 5-1). In the case of the MCU transfers to the LCD controller, the extra control line is the MCUs RD5 pin used to awaken the LCD controllers CPU with a falling-edge INT0 interrupt. Some other devices use the extra input as a chip enable, ignoring the wiggling SCK and SDO lines except when enabled. However it is used, this extra input to a device must serve the device in the manner specified in that devices data sheet.
Section 17.3
245
MCU
Chip select
CS
SPI Control Circuitry SSPIF flag PIR1 SSPBUF Internal shift register SCK (RC3) Serial clock (eight clock pulses are emitted in responce to a write to SSPBUF)
(a) Function of pins CS Clear flag SSPIF Write to SSPBUF SCK Input sampling of SDI pin SDI b7 b6 b5 b4 b3 b2 b1 b0 Flag set upon completion of transfer
246
Chapter 17
SCK CKE = 1, SMP = 0 CKE = 1, SMP = 1 or CKE = 0, SMP = 0 CKE = 0, SMP = 1 (a) SDI sampling times for CKP = 1 (for which SCK idles high)
SCK CKE = 1, SMP = 0 CKE = 1, SMP = 1 or CKE = 0, SMP = 0 CKE = 0, SMP = 1 (b) SDI sampling times for CKP = 0 (for which SCK idles low)
FIGURE 17-5 CKP, CKE, and SMP options for input transfers As in the case of SPI output transfers, an SPI input transfer can use any of several timing alternatives. In addition to the role of the CKP and CKE control bits, input transfers are also controlled by an SMP sampling bit, as shown in Figure 17-4b. If the input transfer makes use of the same CKP = 1 value used for output transfers to the LCD controller, the SCK line will not experience any extra, spurious clock edges that might otherwise throw the input data off by 1-bit time, as discussed in the last section. Figure 17-5a shows the three timing alternatives in the case for which CKP = 1. For example, the chip select pin to the peripheral chip may load its SPI output register so that the most-significant bit is then sitting on its SDO output and, thereby, on the MCUs SDI input. If the peripheral chip is sensitive to rising SCK clock edges, the use of the CKP = 1, SMP = 0 timing choice will have the MCUs SPI sampling its SDI input before the first shift takes place, just as desired. The alternative sampling time choices are obtained by loading SSPSTAT appropriately, as shown in Figure 17-2.
N 2
Section 17.4
247
VDD AD5601 MCU RA5 SCK SDO GND 1 2 3 SYNC VOUT SCK SDI GND VDD 6 5 4 VOUT
FIGURE 17-6 AD5601 DAC circuit connections soldered to the Qwik&Low board using the 0.65 mm surface-mount pattern located on the back of the board. Using the circuit of Figure 17-6, it can be connected to the MCU with 30-gauge wirewrap wire soldered between the AD5601 pins and the pins of the H4 terminal strip. At power-on reset, one of these units typically draws 40 A, but when sent a standby command, the current draw drops to 0.1 A. When sent a power-up command to produce an output voltage, the unit typically draws 60 A, producing an output with the very low output impedance of 0.5 . In standby mode, the output impedance will be 1 k to ground 100 k to ground Three-state (i.e., essentially open circuit) depending on 2 bits of the int value sent to the chip. A block diagram of the DAC is shown in Figure 17-7. The resistor stringmultiplexer combination ensures a monotonic output; that is, an output that always increases as the digital input increases. A relative accuracy specification of 0.5 LSb for the 8- and 10-bit DACs means that the output deviates from a straight line between 0 V and VDD by no more than 0.5 ____ k For the 12-bit DAC, the specification is relaxed slightly to 1 LSb. This DAC family exhibits close to ideal specs, even including a typical settling time of 6 s. To control the DAC, 2 bytes must be sent to the chip over the SPI to load its input register, with the effect shown in Figure 17-8. The chips SPI interface expects to see stable data on its SDI input at the time of the falling edges on its SCK input. This timing dictates the choice of Figure 17-3b. Therefore, before the transfer is initiated, CKE is set to one (from its value of zero dictated by the LCD controllers requirement) as shown in Figure 17-9. The DAC then expects a lowhighlow pulse on its SYNC input. With the circuit of Figure 17-6, this is accomplished by
PORTAbits.RA5 = 1; PORTAbits.RA5 = 0;
VDD 2
248
Chapter 17
VDD
Multiplexer
+ VOUT
With FOSC = 4 MHz, this sequence will produce a 1-s pulse, easily meeting the SYNC pulse-width specification of 20-ns minimum. The chip uses this pulse to reset, or synchronize, its SPI interface. It expects this pulse to be followed by 16 falling edges on its SCK input. It will shift the SDI input into its DAC input register in response to each of these falling edges. In response to the 16th falling edge, the DAC will act on the input register contents, as specified in Figure 17-8. The sequence of events to update the DAC is shown in Figure 17-9. After setting the CKE bit, and pulsing the SYNC input, the MCU clears the SSPIF bit, writes the upper byte to SSPBUF, waits for SSPIF = 1, clears it again, writes the lower byte to SSPBUF, waits for SSPIF = 1, and finally clears the CKE bit to leave this bit in its default state for the LCD controller. If DACREG is the 16-bit int variable to be sent to the DAC, then
DACREGL = DACREG & 0x00FF; DACREGH = DACREG >> 8; //Form lower byte //Form upper byte
Section 17.5
MOSI/MISO Terminology
249
b7 b6 b5 b4 b3 b2 b1 b0 x
0 0 1 1
0 1 0 1
N Power up; generate output Standby with output of 1 k to ground Standby with output of 100 k to ground Standby with open-cicuit output (a) AD5601 input register (for 8-bit DAC) b9 b8 b7 b6 b5 b4 b3 b2 b1 b0 x N Power up; generate output Standby with output of 1 k to ground Standby with output of 100 k to ground Standby with open-cicuit output (b) AD5611 input register (for 10-bit DAC) b11 b10 b9 b8 b7 b6 b5 b4 b3 b2 b1 b0 x N Power up; generate output Standby with output of 1 k to ground Standby with output of 100 k to ground Standby with open-cicuit output (c) AD5621 input register (for 12-bit DAC) x x x x
0 0 1 1
0 1 0 1
0 0 1 1
0 1 0 1
250
CKE
SYNC
SSPIF
Write to SSPBUF
SDO
Chapter 17
Section 17.6
251
SPI master SS
SPI slave
(SCK) (SDI)
SCLK
(SCK) (SDO)
MISO
(SDO)
(SDI)
last section or the INT0 input of the LCD controller) in addition to the original role of framing the transfer. For any device, it is important to read the data sheet to determine how the device expects this input to be used. The Motorola standard defines four SPI modes. Each of these modes specifies a combination of CKP, CKE, and SMP of Figures 17-3 and 17-5 so that data is read by both master and slave half a clock period before data is changed. Two parameters are used to define the four modes, as shown in Figure 17-11: CPOL = 0 means the clock line idles low (same as CKP = 0) CPOL = 1 means the clock line idles high (same as CKP = 1) CPHA = 0 means sample on the leading clock edge CPHA = 1 means sample on the trailing clock edge
The table of Figure 17-11 also lists the values of CKP, CKE, and SMP that produce each mode, derived from a translation of Figures 17-3 and 17-5.
252
Chapter 17
SCLK for CPOL = 0 SCLK for CPOL = 1 MISO & MOSI for CPHA = 0 MISO & MOSI for CPHA = 1 b7 b7 b6 b6 b5 b5 b4 b4 b3 b3 b2 b2 b1 b1 b0 b0
Clock edge upon which data is read rising edge falling edge falling edge rising edge
Clock edge upon which data is changed falling edge rising edge rising edge falling edge
CKP 0 0 1 1
CKE 1 0 1 0
SMP 0 0 0 0
FIGURE 17-11 SPI mode numbers and correlation to CKP, CKE, SMP
VDD
VDD MCU RB4 SPI (RC3) SCK (RC4) SDI (RC5) SDO GND SCLK MISO MOSI CS
VDD ADT7301
(a) Schematic
Section 17.6
253
Supply voltage Accuracy Resolution Operating temperature range Supply current Converting Not converting Shutdown mode Conversion time Package (b) Features
+2.7V to 5.5V 0.5C from 0C to 70C 0.03125C = 0.05625F -40C to +150C 1600 A, typical 190 A, typical 0.2 A, typical 208 s 6-lead SOT-23 or 8-lead SOIC
Output 11 1111 1111 1111 00 0000 0000 0000 00 0000 0000 0001 00 0000 0010 0000 00 0100 0000 0000 (c) Conversion examples
FIGURE 17-12 (continued) a conversion is high, a conversion once per second with the chip otherwise shut down produces an average current draw on the coin cell supply of only
10 1,000
or
TENTHS = (10 RAWTEMP) >> 5; //Temperature in units of 0.1 degree
254
CS
SSPIF
SCLK 0 Wakeup and convert command = 0x00 followed by 0x00 Shutdown command = 0x20 followed by 0x00 (a) Two commands 0 0 0 0 0 0 0 0 0 0 0 0 0 0
MOSI
CS
SSPIF
Chapter 17
MOSI
MISO
FIGURE 17-13 SPI use with ADT7301 with Mode 3 operation (CKP=1, CKE=0, SMP=0)
Section 17.6
255
PROBLEMS
17-1 DAC Output a) Write a little InitDAC function that sends a standby command to the AD5601 digital-to-analog converter described in Section 17.4 for an output impedance of 1 k and a current draw of less than 1 A. Assume the chip is connected to the MCU with the circuit of Figure 17-6. b) If you are willing to add a call of this InitDAC from the Initial function for all of the code that subsequently uses this augmented board, follow the directions at the beginning of Section 17.4 to add the part and to wire the connections. (Without running InitDAC, any code that uses the board will suffer an extra current draw of about 40 A.) c) Write a DAC.c program that powers up the pot and keeps it powered up. Then read the 8-bit output of the pot and write it to the DAC every tenth of a second. Using two DMMs, measure the DC voltage of the DAC output relative to the pot output and also measure the pot voltage as you turn the pot from full CCW to full CW. Make a plot of this voltage difference versus the pot voltage over the full range of the pot output. 17-2 Temperature sensor a) As was done for the last problem, and for the same reason, write a little Init7301 function that sends the shutdown command described in Figure 17-13a assuming the circuit connections of Figure 17-12 (and with no change in the SPI initialization of SSPSTAT and SSPCON1 already carried out for the LCD). b) Subject to the same caution as was raised in Part (b) of the last problem, add an ADT7301 to the SOIC surface-mount pattern on the front of the Qwik&Low board, being careful to avoid the use of any pins used by a part (such as the DAC for the last problem) on the back of the board. c) Write a TenthCent function that, once a second, initiates a conversion. One loop time later it retrieves the converted result while simultaneously shutting the chip down. Then the function displays the temperature with a format exemplified by
27.3C
right justified on the LCD. Assume that the temperature will never exceed 99.9C. d) Write a TenthFahr function analogous to the function of Part (c) that will display the Fahrenheit temperature with a format exemplified by
104.5F
Appendix
A1
QWIKBUG PROGRAM DEBUGGER
By Ryan Hutchinson
A1.1 INTRODUCTION
QwikBug is a small utility used to aid developers in the testing and debugging of C programs compiled for use on the Qwik&Low board. QwikBug was developed for the PIC18F4321 series of 8-bit PIC microcontrollers and makes full use of the PICs built-in background debug mode (BDM).
A1.1.1 Architecture
There are two components to QwikBug: the QwikBug kernel, which is a program that resides in the PICs program memory, and the QwikBug software interface, which provides the graphical user interface from a standard Windows-based PC. The QwikBug kernel and software interface communicate using the computers serial port, over a standard RS-232 connection. A custom protocol was developed to facilitate communications between the two components. This simple protocol defines many standard debugging commands, such as run, step, and add breakpoint. The protocol also defines commands that allow the QwikBug kernel to dynamically program the PICs flash memory using the serial port. This allows the user to download a C program to the PIC without any additional hardware or software.
256
Section A1.1
Introduction
257
QwikBug uses the PGC (Program Clock) input pin on the microcontroller to initiate BDM (Background Debug Mode). This same pin is used by Microchips ICD2 programmer/debugger to communicate to the PIC. This is accomplished by tying the RX input pin that is used by the PICs UART to the PGC input pin. In this fashion, whenever data is received by the PIC on the UART, the PGC pin will trigger the microcontroller to enter BDM. The QwikBug kernel makes use of the PICs low-power features by placing itself into sleep mode when idle. Because of this, the software interface will wake up the chip when transmitting commands by first sending a wake-up byte that is ignored by the kernel and is only used to awaken the chip and return it to BDM. Since the QwikBug kernel is no different from any other program that is written for a PIC microcontroller, it requires the use of various PIC resources to function. The goal behind the QwikBug program was to make it as non-intrusive as possible and to use the minimum amount of resources so that a user program could operate as if it were the only program running on the microcontroller. The QwikBug kernel occupies 1,536 bytes (768 words) of the PICs memory. The kernel is located high in the PICs program memory starting at address 0x1A00 and extends to the very end of its memory at address 0x1FFF. Since the PIC18LF4321 has a total of 8,192 bytes (4,096 words) of program memory available, this leaves 6,656 bytes (3,328 words) of program memory for the users programs. QwikBug also requires some RAM registers to operate. It utilizes a total of 56 bytes of RAM to perform its operations. As with program memory, these 56 bytes are located high in the PICs address range to prevent collisions with user program variables. The RAM used by the QwikBug kernel begins at address 0x1C8 and extends through address 0x1FF. Since all RAM used by QwikBug is above 0x0FF, it makes exclusive use of bank 1 for all of its operations. As long as the users programs do not write to registers in the range specified above, QwikBug can operate as intended. If the registers shared by QwikBug are written to, however, unexpected results may occur. The third resource that is shared by QwikBug and by user programs is the UART. QwikBug uses both the transmit and receive functionality of the UART to communicate behind the scenes with the QwikBug software interface. QwikBug requires exclusive utilization of the UARTs receive buffer, making it unavailable to the user except by moving a jumper on header H1, thereby assigning RX to an add-on user connection. The transmit buffer, however, can be shared between the two. The incoming user data is separated from communications-related data by the software interface and presented to the user via the QwikBug console. To accomplish the seamless transition from BDM to user code, QwikBug shadows many of the PIC special function registers (SFRs). This means that many of the timerand peripheral-related SFRs are saved to temporary locations before being modified by QwikBug. Once QwikBug returns from BDM, the registers are restored and operations can continue as if the user program was never interrupted. QwikBug also uses a clock frequency and serial baud rate that could be different from the users. When BDM is first entered, QwikBug shadows the SFRs related to the clock speed and serial baud rate and uses its own, pre-defined values so that communications with the software interface is always possible.
258
Appendix A1
A1.1.2 Features
The QwikBug software interface was designed to be lightweight and easy-to-use while still providing powerful debugging features. The following features are all part of the QwikBug package: Simple, free debugging interface Run, pause, and single-step through C source code Load and erase programs from a C18-compiled hex file using the PICs UART Monitor watch variables in multiple display formats, variable types, and array sizes Modify watch variable values while program execution is suspended Soft reset of program to initial program vector Add a single breakpoint to stop execution at a specific C source line Quickly search through C source code by navigating to subroutines Display user data received on the PCs serial port using the built-in console tab
A1.2 INSTALLATION
A1.2.1 Prerequisites
The following prerequisites are required to run the QwikBug software interface: PC running Windows XP or Windows Vista Standard PC serial port via any COMi address, from COM1 through COM7 The following prerequisites are required to install the QwikBug kernel: Qwik&Low board Microchip PICkit 2 programmer QwikProgram 2 software installed (found online at www.qwikandlow.com) Standard PC USB port
Note that QwikBug could potentially be installed on a PIC18F4321 chip that was not part of the Qwik&Low board. However, QwikBug was designed to work with the Qwik&Low board and this guide will only cover installation for Qwik&Low boards.
Section A1.2
Installation
259
Attach the PICkit 2 programmer to the computers USB port. Attach the other end of the PICkit 2 to H5 on the Qwik&Low board. This header also has the label MCU PICkit 2 and is specifically for the PIC18LF4321 on the Qwik&Low board. A 6-pin male-to-male header strip is required to connect the female end of the PICkit 2 to the female header on the Qwik&Low board. Ensure that the arrow on the board lines up with the arrow on the PICkit 2. Verify that power is turned off to the Qwik&Low board and run QwikProgram 2 on the computer. The following window should appear:
Verify that the PIC18F4321 was detected as labeled in the QwikProgram 2 status bar before proceeding. If the PIC was not detected, try disconnecting and reconnecting the PICkit 2 from the USB port and running Detect from QwikProgram 2s Device menu. Select File -> Open hex file. . . from the QwikProgram 2 menu. Locate the previously downloaded HEX file and click Open. The window should now look as shown:
Click the Write button once the HEX file has been loaded. This will write the QwikBug kernel to the proper vector location on the PIC. If the verification of the write did not succeed, reseat the PICkit 2 connection and try again. Now the QwikBug kernel has been installed and the QwikBug software interface can be used to communicate with the kernel residing on the PIC.
260
Appendix A1
Launch the setup program. If QwikBug has already been installed on the computer then it will prompt to uninstall the old version before proceeding. Otherwise, click Next after selecting the desired options.
Select the COM port that will be used to communicate with the QwikBug kernel from the drop-down list. Click Next to continue installation.
Select the installation folder for QwikBug. The default installation folder is recommended. Click Install to begin the installation of the software. After installation has successfully completed, click Close. The QwikBug software interface has now been installed. After installation, it is possible to tweak particular QwikBug settings. To do so, open the settings.xml file located in the QwikBug installation directory chosen above using a standard text editor such as Notepad. To change the setting values, modify the value attribute for the desired XML element. Caution; modification
Section A1.2
Installation
261
of the name or type attributes or other aspects of the file could corrupt the XML file and cause QwikBug to malfunction. The table below outlines the settings that can be tweaked:
Setting Name COMPort Default Value (chosen during setup) Description The COM port that QwikBug uses to communicate with the kernel. This value was created during setup and can be changed if needed. The serial port baud rate used to communicate with the kernel. This setting should generally not be changed unless the QwikBug kernel is modified to also use the same baud rate. The amount of time in milliseconds to wait before a timeout error occurs when issuing a command to the QwikBug kernel.
BaudRate
19,200 baud
SerialTimeout
300 ms
262
Appendix A1
Progress bar
Interaction with the QwikBug user interface is identical to that of most commonly used Windows programs. Several of the popup windows that are part of the QwikBug program are set as topmost windows and will always remain on top of other windows while they are open. This is to help the user to quickly relocate the popup windows when needed.
Section A1.4
Loading a Program
263
HEX and COD files must be in the same directory as the LST file and have the same beginning file name or QwikBug will produce an error.
Once the list file has been processed, the source code for the C program will appear in the source box. The progress bar will show the progress of the file as it is being opened. Note that the state label shows Disconnected, indicating that the user program has not yet been loaded into the PIC. Ensure that the Qwik&Low board is powered on and that the serial cable is properly connected from the PC to the Qwik&Low boards female DB-9 serial port connector. To load the program into the PIC, select Load from the Program menu or click the Load toolbar button. The progress bar should begin to increase as the program is downloaded over the serial port. If there was an error downloading, the following message box will appear:
This error message indicates that the software interface was unable to communicate with the QwikBug kernel. If this error occurs, check the power to the PIC and the serial port connections. Also verify that the QwikBug kernel has been properly installed as described in the previous section.
264
Appendix A1
Upon successful load of the program, the state label will change to Paused and the status label should read Program paused [Reset executed]. Note that the Reset , Run , and Step toolbar buttons are all now enabled. Refer to the associated sections to learn about the use of all of QwikBugs debugging features.
Once in run mode, watch variable values will not be updated until the PIC reaches a paused state. If the PIC reaches a breakpoint while running it will also enter the paused state. The PIC can also be reset while in the running state.
A1.5.2 Pausing
No user program code is executed by the PIC while in the paused state. To place the PIC in pause mode after a program has been loaded into program memory, select toolbar button. The PIC can be Pause from the Debug menu or click the Pause
Section A1.5
Program Control
265
paused while it is in run or step mode. Paused is also the default state of the PIC after coming out of reset or performing a program download. The state label will change to Paused while in paused mode and will be highlighted yellow.
The values of the watch variables will be updated as soon as the PIC is placed in the paused state. While in pause mode, breakpoints can be added or removed and watch variables can be added. It is also possible to reload the program into the PIC while in pause mode or to erase the program memory. From pause mode, the user can place the PIC in run mode or step mode. While the PIC is in the paused state, a yellow highlight will appear in the source box to let the user know what code is about to be executed. It is important to remember that the line that is highlighted has not yet been executed or has been partially executed, as is the case for C source lines that contain multiple instructions should the PIC be paused while in the middle of executing the line. The figure below shows an example of a highlighted C source line just before execution:
A1.5.3 Stepping
While the PIC is stepping, code is being executed one instruction at a time. The PIC will continue to step until its program counter reaches a value that is outside the range of the C source code line. There are generally multiple instructions that are executed for each line of C source code. To place the PIC in step mode after a program has been loaded into program memory, select Step from the Debug menu or click the Step toolbar button. The PIC can be stepped while it is in pause mode only. The state label will change to Stepping while in step mode and will be highlighted orange.
If a C source line contains loops that cause the line to continually execute, it could take significant time before the PIC steps out of one C source code line. Because of this, QwikBug allows the user to discontinue step mode by selecting Pause from the
266
Appendix A1
Debug menu or clicking the Pause toolbar button. This will cause the PIC to suspend single-step execution and the state will change back to paused.
A1.5.4 Resetting
QwikBug provides a soft reset feature. To issue a soft reset to the PIC, select Reset from the Program menu or click the Reset toolbar button. This will cause the program counter to reset to the default initialization vector of 0x0000. Additionally, all PIC special function registers (SFRs) and RAM values will be reset to their initial states. A reset can be executed while the PIC is in either run or paused mode. It cannot be executed while the PIC is stepping. QwikBug will return the PIC to the paused state at the initialization vector after a reset.
A1.6 BREAKPOINTS
QwikBug provides the user with a single breakpoint that can be placed on any line of C source code that contains PIC instructions. Breakpoints cause an executing PIC program to halt when the program counter reaches a specified value.
Section A1.7
Watch Variables
267
A line that has a breakpoint associated with it will be highlighted red as shown below:
Note that only one breakpoint is available with QwikBug. If another breakpoint already exists when the Add Breakpoint selection is clicked, the previous breakpoint will be automatically cleared and the newly selected breakpoint line will be highlighted red. Also note that source lines that do not have any PIC instructions associated with them (e.g., comment lines) cannot have breakpoints added to them. If a line that is not associated with any instructions is selected, the Add Breakpoint option will be grayed-out and disabled from the right-click menu.
268
Appendix A1
Once Add Watch is selected, the variable will be added to the watch grid. The variable will be added with the default variable type and display type, and will be initialized with an array size of one element, meaning that the variable is not an array. Only one copy of the variable can exist at a time in the watch grid, and QwikBug will ignore requests to add the same watch variable multiple times. The figure below shows the watch grid after the example variable has been added for watching:
Now that the variable exists in the watch grid the value will be updated whenever the PIC enters the paused state. The Type, Array, and Display columns in the QwikBug watch grid will need to be manually updated for the new watch variable since they will all be set to the default values. Refer to the section on variable and display types below for more information on these columns.
Section A1.7
Watch Variables
269
If multiple rows were selected, all of the selected watch variables will be removed from the watch grid. Multiple rows can be selected at a time by clicking the leftmost column and dragging the mouse to select the desired rows. It is also possible to use the keyboards Del key to quickly delete selected rows from the watch grid. To clear all of the watch variables that are shown in the watch grid, select the Clear All option after right-clicking on the watch grid.
270
Appendix A1
The add watch variable popup is divided into two list boxes: user-defined variables and PIC special function registers. The user-defined variables list box contains all globallydefined C variables that are user defined in the source program. The PIC special function registers list box contains all of the defined SFRs that can be read from or written to on the PIC18LF4321. The variables that are currently being watched are highlighted in the appropriate list box, as shown in the example above. To add or remove a watch variable simply click on the variable name in either list box. If the variable is not being watched, it will become highlighted and will be added to the watch grid. Conversely, if the variable is currently being watched, the highlight will be removed from the list box and the variable will be removed from the watch grid.
Note that the variable type applies to each element of an array. Structures and other multi-type variables are not supported by QwikBug. The variable type for PIC SFRs cannot be modified and will be highlighted gray because all SFRs are single-byte registers and thus have an inherent type of char. The Array column in the watch grid displays the number of elements contained in the associated watch variable. If the variable consists of only one element, the Array column will display No, implying that the watch variable is not an array. To change the number of elements in a watch variable, click on the cell button in the column. The following popup will appear:
Section A1.7
Watch Variables
271
To make the variable an array of elements, check the Array box on the popup. Then, select the total length of the array and click OK to update the watch variable. Note that the Array column will be disabled and grayed-out for SFRs because they can only be single-byte registers. The Display column determines how the watch variables value in the Value column should be displayed to the user. The table below lists all of the possible display type options as well as an example display of each for a single-element char:
Display Unsigned Signed Hex Binary ASCII Example 165 91 0xA5 10100101 G
For variables that have been defined as arrays, the displayed value will be commaseparated with each element following the display formatting shown above. For ASCII display types, only the least significant byte is used to determine the ASCII character. For variables types larger than a char this means that all of the upper bytes are ignored when displaying the value as ASCII.
272
Appendix A1
For variables that are configured as arrays, a row will be present for each element in the array and the associated index number is shown in the column to the left of an elements value. For ASCII display types only one row will be shown and the value is treated as a contiguous string. Click on each of the variables elements that are to be modified and type in a new value for the element. Be sure to use the same display format when overwriting an elements value. QwikBug will verify all values before they are written to the PIC. If the value entered cannot be interpreted for the chosen display type or if the value is outside the range of the variable type, the old value will remain in the elements row. Once all element values have been modified to the desired new values, click the OK button to have QwikBug update the PIC with the new values. The Value column in the watch grid will then be updated with the variables new value(s) once they have been successfully read back from the PIC. To discard any modifications simply click the Cancel button on the popup window and the variable will not be written to.
Section A1.8
Additional Features
273
Select the file to save the watch variable configuration to and click the Save button. The name of the file is insignificant to QwikBug and is only used to help the user keep track of multiple watch variable export files. Once a configuration has been exported, the file can be imported by selecting Import Watch Variables from the File menu. The following dialog will appear:
Select the file that contains the configuration that should be imported and click the Open button to begin the import process. Watch variables can only be imported after a list file has been opened. QwikBug will clear all current watch variables out of the watch grid once an import is initiated and will add the variables from the exported file to the watch grid. QwikBug does not associate watch variable export files with specific list files. Therefore, it is possible to import any watch variable configuration with any list file. QwikBug will check each variable during an import to ensure that it exists in the current source program. QwikBug will not add the variable to the watch grid if the variable name does not exist or if there is a mismatch between the addresses of the exported variable and the current programs variable.
274
Appendix A1
the Debug menu; it will be opened as the topmost window. The popup window will display a list of all subroutine symbols that QwikBug has located in the source file:
Double-clicking on the name of the subroutine in the list box will scroll the source box to the location of the first C source line in the selected subroutine and highlight the line.
Click on the abnormal stop source indicator to display a popup window with details on the abnormal stop source. An example is shown below:
Section A1.8
Additional Features
275
Click the OK button to close the popup window and clear the abnormal stop source indicator warning. Click Cancel to close the popup window but keep the abnormal stop source indicator shown on the main QwikBug window. When an abnormal stop occurs, the PIC will generally be in a reset condition and the user can resume execution from the reset vector or correct the reason for the abnormal stop source in the C source code.
276
Appendix A1
To open any of the previous files, simply click on its name in the File menu. The most recent file is positioned at the top of the list and the oldest is positioned at the bottom.
Both the keyboard shortcut and the alternate keyboard shortcut provide the same functionality. Note that some of the keyboard shortcuts that use Ctrl will not work with the cursor positioned in the main source code text box. Click outside the text box to change the focus so that these keyboard shortcuts will work.
Appendix
A2
278
Appendix A2
Program address Program memory (8192 bytes) 13 bits CPU Instruction 16 bits
Operand address 12 bits Operand memory (RAM and Special Function Registers)
Data 8 bits
and fetches one of the CPUs many 2-byte instructions during each CPU clock cycle. These fetches of 1-cycle instructions are occasionally augmented by the fetches of 2-cycle, 4-byte instructions. At the same time that the CPU is fetching one instruction, it is executing the instruction fetched during the previous cycle. The Harvard architecture has a twofold impact on the performance of the CPU relative to the performance of a single-bus 8-bit microcontroller: The wide 16-bit instruction bus can fetch instructions with half the fetch cycles and thus twice the speed for a given clock rate. The time required to execute instructions is largely hidden beneath the ongoing fetching of successive instructions rather than requiring additional cycles. The effect of the pipelined operation can be explained via the four-line qwik.lst example of Figure A2-2 derived from the T3.lst file. The left column lists the first of the two addresses (i.e., 0116 and 0117) holding an instruction. The next column lists the 2-byte opcode (i.e., 0E2C) that identifies the instruction to the CPU. The next two columns list the assembly language instruction(s) that the compiler has selected to implement the source file line shown to the right. That is,
MOVLW MOVWF MOVLW MOVWF 0x2C ALIVECNTL 0x01 ALIVECNTH
The CPU fetches each 2-byte instruction during its 1-microsecond fetch cycle. Furthermore, as the MOVWF instruction is being fetched, the preceding MOVLW instruction is being executed.
Section A2.4
F/W Distinction
279
ALIVECNT = 300;
// Blink immediately
or a
MOVLB 0x01
instruction to load the Bank Select Register with the number to be used as the upper nibble (i.e., upper four bits) of the full 12-bit address. An application making use of more than 128 bytes of RAM is likely to use indirect addressing to access one or more large arrays. These would extend over the RAM locations that cannot be reached by access-bank direct addressing. Cody Planteens QwikLst utility suppresses the a = 0 parameter of an access-bank directly addressed RAM variable. The result is one less parameter in the .lst file calling for interpretation.
280
Appendix A2
Mnemonic ADDLW ADDWF ADDWFC ANDLW ANDWF BC BCF BN BNC BNN BNOV k
Description
Words 1 1 1 1 1 1 1 1 1 1 1
Cycles 1 1 1 1 1 2 or 1 1 2 or 1 2 or 1 2 or 1 2 or 1
Add WREG and f, putting result into F or W Add WREG and f and carry bit, putting result into F or W AND literal value into WREG AND WREG with f, putting result into F or W If carry or if no borrow (C=1), then branch to labeled instruction Clear bit b of register f, where b = 0 to 7 If negative (N=1), then branch to labeled instruction If no carry or if borrow (C=0), then branch to labeled instruction If 0 (N=0), then branch to labeled instruction If no overflow of signed-number operation, then branch to labeled instruction If not zero (Z=0), then branch to labeled instruction Branch to labeled instruction Set bit b of register f, where b = 0 to 7 Test bit b of register f, where b = 0 to 7; skip if clear Test bit b of register f, where b = 0 to 7; skip if set Toggle bit b of register f, where b = 0 to 7 If overflow of signed-number operation, then branch to labeled instruction If zero (Z=1), then branch to labeled instruction Call subroutine Call subroutine; copy (WREG) WS, (STATUS) STATUSS, (BSR) BSRS Load f with 0x00 Clear watchdog timer
1 1 1 1 1 1 1
2 or 1 2 1 2 or 1 2 or 1 1 2 or 1
BZ CALL CALL CLRF CLRWDT COMF CPFSEQ CPFSGT CPFSLT DAW DECF DECFSZ DCFSNZ GOTO INCF INCFSZ INFSNZ IORLW IORWF LFSR MOVF
1 2 2 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 2 1
2 or 1 2 2 1 1 1 2 or 1 2 or 1 2 or 1 1 1 2 or 1 2 or 1 2 1 2 or 1 2 or 1 1 1 2 1
f,F/W f f f
Complement f, putting result into F or W Skip if f is equal to WREG Skip if f is greater than WREG (unsigned compare) Skip if f is less than WREG (unsigned compare) Decimal adjust binary sum in WREG of two packed BCD numbers
f,F/W f,F/W f,F/W label f,F/W f,F/W f,F/W k f,F/W i,k f,F/W
Decrement f, putting result into F or W Decrement f, putting result into F or W; skip if zero Decrement f, putting result into F or W; skip if not zero Go to labeled instruction Increment f, putting result into F or W Increment f, putting result into F or W; skip if zero Increment f, putting result into F or W; skip if not zero Inclusive-OR literal value into WREG Inclusive-OR WREG with f, putting result into F or W Load FSRi with address of operand Move f to F or W
Section A2.4
F/W Distinction
281
Description
Words 2 1
Cycles 2 1
Move literal value to BSR<3:0>, where k = 0 to 15, to set the bank for direct addressing with a=1 Move literal value to WREG Move WREG to f Multiply unsigned WREG with literal value, putting result into PRODH:PRODL Multiply unsigned WREG with f, putting result in PRODH:PRODL Change sign of a twos-complement-coded one-byte number No operation Discard address on top of stack Push address of next instruction onto stack
k f k
1 1 1
1 1 1
f f
1 1 1 1 1 1 1 1 1
1 1 1 1 1 2 1 2 2
label
Call subroutine Software reset to same state as is achieved with the MCLR input Return from interrupt; reenable interrupts
FAST
Return from interrupt; reenable interrupts; restore state from shadow registers (WS) WREG, (STATUSS) STATUS, (BSRS) BSR Return from subroutine, putting literal value into WREG Return from subroutine
1 1 1
2 2 2
C,OV,N,Z
FAST
Return from subroutine; restore state from shadow registers (WS) WREG Copy f into F or W; rotate left through carry bit (9-bit rotate left) Copy f into F or W; rotate left without carry bit (8-bit rotate left) Copy f into F or W; rotate right through carry bit (9-bit rotate right) Copy f into F or W; rotate right without carry bit (8-bit rotate right) Load f with 0xFF Normally enter sleep mode; if IDLEN=1, enter idle mode
1 1 1
1 1 1
RRNCF
f,F/W
N,Z
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 2 2
Subtract f and
Subtract WREG from literal value, putting result into WREG Subtract WREG from f, putting result into F or W Subtract WREG and borrow bit from f, putting result into F or W Swap four-bit nibbles of f, putting result into F or W Read from program memory location pointed to by TBLPTR into TABLAT Read from program memory location pointed to by TBLPTR into TABLAT, then decrement TBLPTR Read from program memory location pointed to by TBLPTR into TABLAT, then increment TBLPTR Increment TBLPTR, then read from program memory location pointed to by TBLPTR into TABLAT Test f; skip if zero Exclusive-OR literal value into WREG Exclusive-OR WREG with f, putting result into F or W
TBLRDPOSTDEC
TBLRDPOSTINC
1 1 1
2 or 1 1 1
N,Z N,Z
282
Appendix A2
15 Instruction opcode
9 8 7
(a) Nine bits of an instruction identifying the source of an operand. 8-bit address 128 RAM addresses 128 Special Function Register addresses (b) Access bank direct addressing (a = 0). Full 12-bit address Bank Select Register, BSR (Access bank RAM) Bank0 12-bit effective operand address Instruction opcode More RAM 512 bytes of RAM 1 a f Still more RAM Bank1
to the variable or, alternatively, to the CPUs WREG working register, as shown in Figure A2-5. The .lst file generated by the linker shows both the a parameter of the last section and the F/W parameter of this section as 0x0 or 0x1
Section A2.6
Counting Cycles
283
F Addressed location Instruction operation WREG W (a) One-operand instruction (e.g., INCF).
F Addressed location Instruction operation WREG W (b) Two-operand instruction (e.g., ADDWF).
The QwikLst utility suppresses the a = 0 parameter (as discussed in the last section) and converts the 0 or 1 value listed for the F/W parameter into a W when the destination of the operation is WREG and an F when the operation returns the result back to the source address used by the instruction.
284
000178 00017a 00017c 00017e 000180 000182 000184 000186 000188 00018a { 0x5,0x0 0x6,0x0 0x83,0x4,0x0 PORTDbits.RD4 = 1; } 0x0 (a) Listing file void BlinkAlive() { PORTDbits.RD4 = 0; if (++ALIVECNT == 400) } // Turn on LED for one looptime ALIVECNT = 0; // Reset ALIVECNT
9883 2a05 0e00 2206 0e90 1805 e106 0e01 1806 e103
BCF INCF MOVLW ADDWFC MOVLW XORWF BNZ MOVLW XORWF BNZ
0x83,0x4,0x0 0x5,0x1,0x0 0x0 0x6,0x1,0x0 0x90 0x5,0x0,0x0 0x192 0x1 0x6,0x0,0x0 0x192
void BlinkAlive() { PORTDbits.RD4 = 0; if (++ALIVECNT == 400) // Turn off LED // Increment counter and return if not 400
000192
0012
RETURN
0178 017A 017C 017E 0180 0182 0184 0186 0188 018A { ALIVECNTL ALIVECNTH PORTD,4 } } (b) Translation into qwik.lst ALIVECNT = 0; PORTDbits.RD4 = 1;
9883 2A05 0E00 2206 0E90 1805 E106 0E01 1806 E103
BCF INCF MOVLW ADDWFC MOVLW XORWF BNZ MOVLW XORWF BNZ
PORTD,4 ALIVECNTL,F 0x00 ALIVECNTH,F 0x90 ALIVECNTL,W L003 0x01 ALIVECNTH,W L003
Appendix A2
0192
Section A2.7
Flag Bits
285
00E2 00E4 00E6 00E8 00EA 00EC 00EE 00F0 00F2 00F4 00F6 00F8 00FA 00FC
0E50 MOVLW 6E0F MOVWF 0EC3 MOVLW 6E10 MOVWF 060F L002 DECF 0E00 MOVLW 5A10 SUBWFB 500F MOVF 1010 IORWF E003 BZ 0000 NOP 0000 NOP D7F7 BRA 9CD0 L001
0x50 DELAYL 0xC3 DELAYH DELAYL,F 0x00 DELAYH,F DELAYL,W DELAYH,W L001
Delay(50000);
L002
The BRA instruction tells the CPU to branch to the program address labeled L002 that holds the instruction
00EA 060F L002 DECF DELAYL,F
This instruction decrements the low byte of the 2-byte variable DELAY. Every instruction in this loop of instructions executes in one cycle except BRA (2 cycles) and BZ (2 or 1 cycles). The BZ instruction tests the result of an operation that preceded it. If that 8-bit result was 0x00, the CPU will take two cycles as it branches to address 0x00FC. However, while the CPU is looping for the first 49,999 times, the BZ instruction does not branch. Thus, the number of cycles taken to execute one of these 49,999 loops (beginning with the DECF instruction and ending with the BRA instruction) is 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 2 = 10 cycles
286
Appendix A2
An add operation of two signed 1-byte numbers expressed in twos-complement code will set the OV (overflow) bit if the result is larger than 127, the largest 1-byte signed number. For a source file that adds two 1-byte numbers, the C compiler will look to see if they are defined as unsigned or signed numbers and then test the C bit or the OV bit to determine whether an overflow occurred. Subtract operations of unsigned and signed numbers represent a borrow condition with the complement of the C and OV bits. That is, a borrow resulting from an unsigned subtraction will produce C = 0. A borrow resulting from a signed subtraction will produce OV = 0. The N status bit signifies whether the result of an operation on signed numbers is negative (N = 1). If the result is 0 or positive, then N = 0.
'P' 'R' 'E' 'S' 'S' ' ' 'P' 'B' ' '
Section A2.9
CPU Registers
287
;FSR1L = low byte of address of MSGSTRING ;FSR1H = high byte of address of MSGSTRING
1,MSGSTRING
MOVFF
POSTINC1,SSPBUF
FIGURE A2-8 (continued) The five operations associated with indirect addressing are INDFi, POSTINCi, POSTDECi, PREINCi, and PLUSWi, where i = 0, 1, or 2 to identify which of the three pointers, FSR0, FSR1, or FSR2, is to be used for carrying out the operation. If the operand is INDFi, the CPU accesses the location pointed to by FSRi. If the operand is POSTINCi, the CPU accesses the location pointed to by FSRi and then increments FSRi. If the operand is POSTDECi, the CPU accesses the location pointed to by FSRi and then decrements FSRi. If the operand is PREINCi, the CPU first increments FSRi and then accesses the location pointed to by the incremented FSRi. Finally, if the operand is PLUSWi, the CPU temporarily adds its working register, WREG (treated as a signed number), to the address in FSRi to form the address of the instruction operand. It should be pointed out that the address associated with any one of the five indirect addressing operations is not a physical register. Rather, the CPU interprets an access of the address as a signal to carry out the specified indirect addressing operation. Thus the instruction
INCF INDF1,F
288
WREG
Bank Select Register FSR0H FSR0 FSR1H FSR1 FSR2H FSR2 PRODH PROD PCLATH Transferred by a read of PCL Transferred by a write to PCL PRODL FSR2L INDF2 POSTINC2 POSTDEC2 PREINC2 FSR1L INDF1 POSTINC1 POSTDEC1 PREINC1 FSR0L INDF0 POSTINC0 POSTDEC0 PREINC0
Increment Temporarily FSRi add WREG to FSRi then access then access PLUSW0
PLUSW1
PLUSW2
Multiplication result
Appendix A2
Program Counter
Section A2.11
289
Any multiply operation makes use of one of the two 8-bit unsigned multiply instructions, MULLW or MULWF, putting the 16-bit result into the CPU registers PRODH: PRODL. Any instruction that operates on the program counter operates on the lower 8 bits, PCL. If that instruction is going to write back to PCL, then, at the same time that the write takes place, the content of PCLATH (Program Counter LATcH) is written into the upper byte of the program counter. Subroutine calls and interrupts nest return addresses onto the 31-level stack. The PIC18 family of microcontrollers has an extended instruction set that can be used to facilitate the extension of the stack into RAM, to support a more powerful compilation. However, for the benefit of QwikBug and also for the benefit of execution speed, that extended instruction set is disabled.
They use the TBLPTRH:TBLPTRL register pair to identify the program memory address that is to be copied to the TABLAT register. They include the option of postincrementing, postdecrementing, or preincrementing TBLPTRH:TBLPTRL. An example of the creation and use of a table is shown in Figure A2-11. This FormHex table expects to be handed a char variable containing a value ranging from 0 to 15. It returns the ASCII-coded hex character corresponding to that value. Thus, in the example whose qwik.lst file segment is shown in Figure A2-11b, the source file line
ASCII = FormHex[NIBBLE];
290
Appendix A2
Selected bytes
Address of byte to be read 13 bits Content of selected address 8 bits (a) Special mechanism for reading from program memory. TABLAT
TBLRD TBLRDPOSTINC
Read from program memory location pointed to by TBLPTRH:TBLPTRL into TABLAT Read from program memory location pointed to by TBLPTRH:TBLPTRL into TABLAT, then increment TBLPTRH:TBLPTRL Read from program memory location pointed to by TBLPTRH:TBLPTRL into TABLAT, then decrement TBLPTRH:TBLPTRL Increment TBLPTRH:TBLPTRL, then read from program memory location pointed to by TBLPTRH:TBLPTRL into TABLAT (b) Special instructions for reading from program memory.
TBLRDPOSTDEC
TBLRDPREINC
FIGURE A2-11 Creation and use of a program memory table to convert a number between 0 and 15 to the ASCII code for its hex representation
/******************************* * Constant table ******************************* */ const char rom FormHex[] = "0123456789ABCDEF";
Section A2.11
291
ASCII = FormHex[NIBBLE];
FIGURE A2-11 (continued) FIGURE A2-12a Special Function Registers and their full hex addresses
ADCON0 ADCON1 ADCON2 ADRESH ADRESL BAUDCON BSR CCP1CON CCP2CON CCPR1H CCPR1L CCPR2H CCPR2L CMCON CVRCON ECCP1AS ECCP1DEL EEADR EECON1 EECON2 EEDATA FSR0H FSR0L FSR1H FSR1L FSR2H FSR2L HLVDCON FC2 FC1 FC0 FC4 FC3 FB8 FE0 FBD FBA FBF FBE FBC FBB FB4 FB5 FB6 FB7 FA9 FA6 FA7 FA8 FEA FE9 FE2 FE1 FDA FD9 FD2 INDF0 INDF1 INDF2 INTCON INTCON2 INTCON3 IPR1 IPR2 LATA LATB LATC LATD LATE OSCCON OSCTUNE PCL PCLATH PCLATU PIE1 PIE2 PIR1 PIR2 PLUSW0 PLUSW1 PLUSW2 PORTA PORTB PORTC FEF FE7 FDF FF2 FF1 FF0 F9F FA2 F89 F8A F8B F8C F8D FD3 F9B FF9 FFA FFB F9D FA0 F9E FA1 FEB FE3 FDB F80 F81 F82 PORTD PORTE POSTDEC0 POSTDEC1 POSTDEC2 POSTINC0 POSTINC1 POSTINC2 PR2 PREINC0 PREINC1 PREINC2 PRODH PRODL RCON RCREG RCSTA SPBRG SPBRGH SSPADD SSPBUF SSPCON1 SSPCON2 SSPSTAT STATUS STKPTR T0CON T1CON F83 F84 FED FE5 FDD FEE FE6 FDE FCB FEC FE4 FDC FF4 FF3 FD0 FAE FAB FAF FB0 FC8 FC9 FC6 FC5 FC7 FD8 FFC FD5 FCD T2CON T3CON TABLAT TBLPTRH TBLPTRL TBLPTRU TMR0H TMR0L TMR1H TMR1L TMR2 TMR3H TMR3L TOSH TOSL TOSU TRISA TRISB TRISC TRISD TRISE TXREG TXSTA WDTCON WREG FCA FB1 FF5 FF7 FF6 FF8 FD7 FD6 FCF FCE FCC FB3 FB2 FFE FFD FFF F92 F93 F94 F95 F96 FAD FAC FD1 FE8
292
Appendix A2
ADCON0 ADCON1 ADCON2 ADRESH ADRESL BAUDCON BSR CCP1CON CCP2CON CCPR1H CCPR1L CCPR2H CCPR2L CMCON CVRCON ECCP1AS ECCP1DEL EEADR EECON1 EECON2 EEDATA FSR0H FSR0L FSR1H FSR1L FSR2H FSR2L HLVDCON
C2 C1 C0 C4 C3 B8 E0 BD BA BF BE BC BB B4 B5 B6 B7 A9 A6 A7 A8 EA E9 E2 E1 DA D9 D2
INDF0 INDF1 INDF2 INTCON INTCON2 INTCON3 IPR1 IPR2 LATA LATB LATC LATD LATE OSCCON OSCTUNE PCL PCLATH PCLATU PIE1 PIE2 PIR1 PIR2 PLUSW0 PLUSW1 PLUSW2 PORTA PORTB PORTC
EF E7 DF F2 F1 F0 9F A2 89 8A 8B 8C 8D D3 9B F9 FA FB 9D A0 9E A1 EB E3 DB 80 81 82
PORTD PORTE POSTDEC0 POSTDEC1 POSTDEC2 POSTINC0 POSTINC1 POSTINC2 PR2 PREINC0 PREINC1 PREINC2 PRODH PRODL RCON RCREG RCSTA SPBRG SPBRGH SSPADD SSPBUF SSPCON1 SSPCON2 SSPSTAT STATUS STKPTR T0CON T1CON
83 84 ED E5 DD EE E6 DE CB EC E4 DC F4 F3 D0 AE AB AF B0 C8 C9 C6 C5 C7 D8 FC D5 CD
T2CON T3CON TABLAT TBLPTRH TBLPTRL TBLPTRU TMR0H TMR0L TMR1H TMR1L TMR2 TMR3H TMR3L TOSH TOSL TOSU TRISA TRISB TRISC TRISD TRISE TXREG TXSTA WDTCON WREG
CA B1 F5 F7 F6 F8 D7 D6 CF CE CC B3 B2 FE FD FF 92 93 94 95 96 AD AC D1 E8
FIGURE A2-12b Special Function Registers and their Access Bank hex addresses
Appendix
A3
293
294
Appendix A3
U3 PIC18LF4321 MCU
H3 PB1 Reset pushbutton NC R12 100 R13 51.1 VDD Banana jacks for digital microammeter +Vin BT1 CR2032 3V coin cell
+
18
VPP/MCLR
C4 0.1 F
C4 0.1 F
7 VDD 28 VDD 6 VSS 29 VSS 17 16 In-cicuit debugger pins PGD (RB7) PGC (RB6) UART RX (RC7)
5 3 2 R3 1 k R2 100 k
TX TP1
44
TX (RC6)
32
T1OSI (RC1)
8-character starburst LCD Varitronix VIM-878-DP-FC-S-LV (Digi-key 153-1113) 20 COM0 19 COM1 18 COM2 17 COM3 SEG0 SEG1 SEG2 SEG3 SEG4 SEG5 35 1 36 2 33 3 24 14 21 15 22 63 62 61 60 58 55 54 53 52 51 8 6 5 4 3 59
C10 0.1 F 10 9
C6 0.1 F
C5 0.1 F
C9 0.1 F C7 0.1 F C8 0.1 F C11 0.1 F LCD test points FOSC/4 TP11 INT0 TP10 INT0 48 SCK TP9 SDI TP8 6390 RC7 TP7
VDD VSS VDD VSS VDD VSS AVDDVDDVSS AVSS 64 LCDbias3 COM0 COM1 COM2 COM3 SEG0 SEG1 SEG2 SEG3 SEG4 SEG5 SEG26 SEG27 SEG28 SEG29 SEG30 SEG31
PGC PGDGND VDD VPP/MCLR
LCDbias2 LCDbias1
FOSC/4
RD5
Serial Peripheral Interface
37
SCK
34
SCK (RC3)
43
SDI RC7
35 32
SDO (RC5)
42 37 NC
41 38
R17 475 k
7
R18 100 k
H5
PGC PGD GND VDD VPP
295
U3 PIC18LF4321 MCU
RE2 RE1 RE0 RD7 RD6 To LCD (RD5) RD4 RD3 RD2
Switched R6 27 power 1 M 26 25 Switched power 5 4 Switched power 3 2 41Switched power 40 RD2 TP5 R7 4.99 k Jumper H2 R9 1 k R8 22.6 k R5 22.6 k VDD AN2
39 RD1 38 RD0 UART RX (RC7) TX (RC6) SDO (RC5) SDI (RC4) SCK (RC3) 1 44 43 42 37
LED1
36 CCP1/RC2 35 Timer1 T1OSI (RC1) oscillator T1OSO (RC0) 32 In-circuit debugger pins PGD (RB7) PGC (RB6) RB5 RB4 CCP2/RB3 INT2 (RB2) INT1/RB1 INT0/RB0 17 16 15 14 11 10 9 8
30 Switched power RA7 F /4 TP6 FOSC/4 (RA6) 31 OSC 24 (AN4) RA5 23 RA4 22 VREF+ TP3 VREF+(RA3) 21 AN2 (RA2) 1 U1 20 AN1 TP4 2 Temp. AN1 (RA1) 19 3 sensor AN0 (RA0) POT1 20 k VDD 7,28 6,29 GND H4 (unpopulated header)
R10 1 M
GND VDD SDI SD0 SCK RC2 RB5 RB4 RB3 RB1 RB0 RA5 RA4 AN2 CCP1 CCP2 INT1 INT0 AN4
VDD GND
296
Quantity
Order Number Qwik&Low board C2,3 C1,4,5,6,7,8,9,10,11 R12,18 R3,9 R7 R5,8 R13 R2,4 R14,15,16,17 R1,6,10 R11 Y1 U3 U2 U4 TQFP-64 LCD controller Microchip TSOC-6 Silicon Serial Number Dallas Semi. TQFP-44 Microcontroller Microchip 32768 Hz watch crystal Abracon 1206 (leave this unpopulated) ABS25-32.768KHZ-T PIC18LF4321-I/PT DS2401P+ PIC18LF6390-I/PT 1.05 3.37 1.52 3.27 Back of board components = LED1 U1 POT1 CON1 LCD1 BT1 BT1X 1/4 Dia hole SW2 H1 H2 H2X H3,5 H4 H6 PB1 RPG1 SW1 SIP100_3P SIP100_17P SIP100_6P SIP100_2P 100 mil shunt (leave these unpopulated) (leave this unpopulated) Male shrouded 5x2 pin header Sealed pushbutton switch Detented RPG-PB switch comb. SPDT toggle switch Four rubber bumpers for feet Assmann Cannon ALPS Mountain Sw. 3M AWHW10G-0202-T-R KSA0M211 LFT EC11G1524402 108-0051-EVX SJ-5003 (BLACK) 0.59 0.34 2.19 1.97 0.13 Remaining components = Total = 0.59 0.34 2.19 1.97 0.52 23.47 41.41 SIP100_3P CR2032 coin cell Nickel plated banana jack 4PDT push-on, push-off switch (leave this unpopulated) Male straight 2-pin header Tyco AMP Sullins 4-103239-0 SPC02SYAN 0.10 0.12 0.10 0.12 CR2032 coin cell holder 2 x 1 8-char starburst LCD Female DB-9 socket POT_HFAA 20K Thumbwheel POT TO-92 Temperature sensor T-1 Red LED, through-hole mounting Panasonic Analog Dev. Panasonic Tyco AMP Varitronix MPD Panasonic Johnson E-Switch LN28RALXU AD22103KTZ EVL-HFAA06B24 5747844-4 VIM-878-DP-FC-S-LV BH32T-C CR2032 108-0740-001 TL4201EEYA 0.38 2.86 2.07 2.72 2.92 1.04 0.25 1.48 2.44 1.05 3.37 1.52 3.27 17.94 0.38 2.86 2.07 2.72 2.92 1.04 0.25 2.96 2.44 1206 1.00M 1/4W 1% thick film resistor Yageo 1206 475K 1/4W 1% thick film resistor Yageo RC1206FR-071ML 1206 100K 1/4W 1% thick film resistor Yageo RC1206FR-07100KL RC1206FR-07475KL 1206 51.1K 1/4W 1% thick film resistor Yageo RC1206FR-0751K1L 1206 22.6K 1/4W 1% thick film resistor Yageo RC1206FR-0722K6L 1206 4.99K 1/4W 1% thick film resistor Yageo RC1206FR-074K99L 0.09 0.09 0.09 0.09 0.09 0.09 1206 1.00K 1/4W 1% thick film resistor Yageo RC1206FR-071KL 0.09 1206 100 ohm 1/4W 1% thick film resist. Yageo RC1206FR-07100RL 0.09 1206 0.1uF 50V X7R ceramic capacitor Yageo CC1206KRX7R9BB104 0.10 0.90 0.18 0.18 0.09 0.18 0.09 0.18 0.36 0.27 1206 15 pf 50V NPO ceramic capacitor BC Comp. VJ1206A150JXACW1BC 0.15 0.30 PCBCART 6.00
Distributor
Designation
Footprint
Part Description
Manufacturer
Cost Each
Total Cost
BC1305CT
Digi-Key
311-1179-1
Digi-Key
311-100FRCT
Digi-Key
311-1.00KFRCT
Digi-Key
311-4.99KFRCT
Digi-Key
311-22.6KFRCT
Digi-Key
311-51.1KFRCT
Digi-Key
311-100KFRCT
Digi-Key
311-475KFRCT
Digi-Key
311-1.00MFRCT
Digi-Key
Digi-Key
535-9166-1
Digi-Key
PIC18LF4321-I/PT
Digi-Key
DS2401P+
Digi-Key
PIC18LF6390-I/PT
Digi-Key
P605
Digi-Key
AD22103KTZ
Digi-Key
P4D2203
Digi-Key
A32117
Digi-Key
153-1113
Digi-Key
BH32T-C
Digi-Key
P189
Digi-Key
J147
Digi-Key
EG1874
Digi-Key
Appendix A3
A26512-40
Digi-Key
A26512-40
Digi-Key
S9001
Digi-Key
A26512-40
Digi-Key
A26512-40
Digi-Key
HRP10H
Digi-Key
855-0017
Allied
688-EC11G1524402
Mouser
108-0051-EVX
Mouser
517-SJ-5003BK
Mouser
297
the RX input. If a transducer is used with a UART output that idles low, the R4 resistor should be removed from the board. One interesting addition to the board is a 12-key (09, *, #) keypad with an 80, 14-pin PIC16F505 microcontroller providing: The decoding of the keypad. A bit-banged UART interface. The CPU clock of the PIC16F505 is 1 MHz 2%. This is sufficiently accurate so that if an output pin is changed appropriately at intervals of 1,000,000/19,200 = 52.08333 s 52 s then the MCU will read this serial input exactly as it would from a 19,200 baud UART. The Timer1 crystal oscillator circuitry shown in Figure A3-1 immediately below the UART circuitry includes pads labeled R11. The intent is to make it possible to add a high-impedance (e.g., 3 M) resistor, to try to get the Timer1 oscillator to run reliably with the
# pragma config LPT10SC = ON
configuration choice. This choice, the reliability of which has not been thoroughly explored, would decrease the current draw of the Timer1 oscillator and the Timer1 counter from about 6 A to 1 A. That low-power oscillator configuration is meant to run with a 5 V supply, not the 3 V supply of the Qwik&Low board. A Microchip application engineer intimately involved with the oscillator design suggested this pullup resistor modification. The unpopulated H4 header provides test points for the pin of a scope probe, to test user-generated outputs (e.g., for a pulse-width measurement on the RC2 or RB0 pin). The unpopulated header also provides solder points for an add-on part installed on the surface-mount pads located on the front or the back of the board. Point-to-point wiring with #30 wirewrap wire can produce a clean job of the addition. Be sure to remove any solder flux, especially if water-soluble (i.e., conductive) flux is used. The prototype area on the board allows DIP parts and discrete parts to be added easily. For surface-mount parts that do not fit on the available surface-mount patterns (e.g., an SOIC part with up to 28 pins), consider the use of one of the surface-mountto-DIP adapters available from www.beldynsys.com. The test points dispersed over the board are designed for the pin probe of a scope. FOSC/4, the CPU clock for each of the PIC microcontrollers on the board, allows a user to gauge the awake/sleep behavior of each chip under varying circumstances. An output pin on the MCU can be employed in a user program to monitor time intervals of interest. In like manner, one of the few free output pins of the LCD controller, RC7, can be monitored. TP7 is such a test point, located near the right edge of the board below TP8. It is unlabeled (but can be probed) on the front of the board. It is labeled on the back of the board. The intent is to be unobtrusive for normal use of the board, but available for users who wish to explore modifications to the LCD controllers program code.
298
Appendix A3
H3
H5
MCU PICkit 2
36 35 34 33 32 31 30 29 28 27 26
LCD PICkit 2
25 24 23 22 21 20 19
LCD Testpoints
1
VDD
1 2
+Vin ON
U1 CR2032
3 2
F /4 OSC TP11
1
SW1 RESET
3 2
INTO TP10
1
SCK TP9
1
PB1
2 1
SDI TP8
H2 LED1
1
10
11
12
13
14
15
16
17
18 1
LCD1
1 2
SW2
4 7 10
H4 GND VDD
16 8 8 8 8 8 8 8 8 8 8 8 8 8 15 8 8 8 8 8 8 8 8 8 8 8 8 8
BT1
11
RX TP2 PC RX RX
3
9 5
Qwik&Low
1 2 1 1
11
14
13
12
11
6 1
H1
1 2 3
RD2 TP5
1
FOSC/4 TP6
10
RPG1
10
TX TP1
7 6 2 4
VDD GND
8 8
SO16 / SOT23
2 8 1 16 8 8 1 8 3 2 3 8 4 5 4 13 8 14 8 2 1 6 15 8
10
CON1
H6
8
5 1 8 6 2 3 6 5 4
12
11
8 2 8 5 4 3
10
RPG/PB
POT1
Ground
50 mil
R13
R18 R17
1 19 20 21 22 23 24
R14
27 28 29 30 31 32 33 34 35 36 3 1 2 1
25
26
C10
1 1
U4
2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49
C6
2
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33
C9
18 1 17 16 15 14 13 12
C5
1 2 7 6 5 4 3 2 1
11
10
R9
2
2 2 1
GND VDD
10
R12
1 2
4 1
3 1
2 1
0 1
1 1 1
2 2 2
1 1 1
2 2 2
SDI SDO SCK RC2/CCP1 RB5 RB4 RB3/CCP2 RB1/INT1 RB0/INT0 RA5/AN4 RA4 AN2
12
11
R10
20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 1 2 1 2 2 1
22
21
23 24 25 1 26 27 28 29 2 30 31
C1
1
11
C4 R11 Y1
1 1 1 2 2 1
32 33
34
35
36
37
38
39
40
41
42
43
44
R4 U3
3 2 1
R2 5 9
1 5 9
2 4
R7 U2
1 6 2 5 3 4
R1
2
6 2 3 2
C3
C2
1 1 3 2
R3 1
1 2 1
6
10
10
R8
8
0.5 mm
1 2 3 4 8 7 6 5 8 8
R6
7
2 8
VDD GND
8 8 4 2 6
2 2 1
1 8
8 5 8
R5
8 1 8 2 3 8 4 8 7 6 5 8 8
0.65 mm
Ground
1 4 5
Rev 5
299
The parts list includes the same Digi-Key part number, A26512-40, for all of the single-in-line (SIP) headers, H1, H2, H3, H4, and H5. This part is actually a 40-pin strip that is designed to be cut with diagonal cutters into headers having the desired number of pins. The parts list shows the Qwik&Low board as being manufactured by PCBCART (www.pcbcart.com), a vendor for having low-cost, high-quality boards made in China with a nominal two-week delivery schedule. The $6.00 price shown in Figure A3-2 was the approximate unit price with an order for 30 boards. The Gerber files on the www.qwikandlow.com website can be used to place an order for boards. However, proceed cautiously! As the artwork of Figure A3-3 for the front and the back of the board illustrates, the surface-mount parts for the back of the board, particularly the two PIC microcontrollers, call for considerable experience in dealing with fine-pitch parts. Also, when completed, a PICkit 2 programmer must be available to program the two chips. Before undertaking such a venture, be sure to consider the alternative of purchasing a built and tested board. See http://www.qwikandlow.com/purchase. Chris Bruhn and Peter Ralston have developed a Performance Verification program, PV.c, available from the www.qwikandlow.com website. This program can be compiled, loaded via QwikBug, and run. It initially sends the serial number from the DS2401 chip (Chapter Fifteen) to the QwikBug console, verifying both the chip and the serial interface. It then runs the LCD display through four quick tests before moving on to display the temperature, and to verify the operation of the Timer1 oscillator and the operation of the potentiometer/ADC combination. If the Timer1 oscillator is working correctly, the middle number on the display will increment every second. The LED also blinks every second. As the potentiometer is turned from full CCW to full CW, the right-hand number changes from 00 to FE or FF. Turning the RPG turns on individual segments of the LCD, verifying that no adjacent pins of the display are shorted together. Turning the RPG clockwise or counterclockwise also increments/decrements a number sent to the QwikBug console. The LED is turned on in response to the pressing of the RPGs pushbutton. The current draw of the board while running this program (with the LED jumper removed and the LCD switched off) is about 20 A.
Appendix
A4
300
CON1X Wall transformer for 12 V @ 0.5 A regulated power supply Input protection circuit R10 1.00 k R1 51.1 k D2 VCC H3 Shrouded header CON1 2.1 mm barrel connector U1 Allegro Microsystems A3967SLB-T (24-pin SOIC) 11 DIR
STEP R2 51.1 k D1 Schottky rectifier VCC 1 REF RESET SLEEP VCC 14 VCC 24 Cuttable link MS2 13 VCC H4 MS2 MS1 (Unpopulated VCC header) Cuttable links 22 3 TB2 Terminal R9 block 10 k B R S S H1 Vm 5 VLOAD1 GND GND VLOAD2 GND GND SENSE1 ENABLE 15 17 R7 (Unpopulated) R6 1.5 C4 0.01 F RC2 2 C2 0.001 F R3 51.1 k POT1 100 k (Unpopulated) RC1 Imotor I 7 VCC 20 + C7 33 F 19 18 PWM 6 + C1 33 F 23 PWM C6 0.001 F R8 51.1 k POT2 100 k (Unpopulated) C9 0.1 F C8 33 F PFD C5 0.1 F U2 3.3 V Voltage regulator MS1 12 MS2 MS1 0 0 1 1 0 1 0 1 K E Y Input header R11 1.00 k
10
TB2X LED
DMM
301
302
and R6, shown in the lower-right corner of Figure A4-1. The Allegro chip is designed to be able to drive each of the two stepper-motor windings with a voltage of up to 30 V and a current of up to 750 mA. When full stepping (the default stepping mode), the current in each winding is approximated by the equation1 0.707 VREF 292 Iwinding = ___________ = ____ mA Rs 8 Rs with VREF = 3.3 V. With Rs = 1.5, Iwinding = 194 mA. When operating in the fullstep mode, both windings are energized with this () current at each step position for a total load of 386 mA for the wall transformer. The Allegro chip carries out full stepping by alternately reversing the current in one winding and then the other winding. The stepper motor listed in the parts list of Figure A4-2 steps 200 full steps per revolution. Even finer resolution (i.e., 400 s/r, 800 s/r, or 1,600 s/r) can be achieved by adding a 2 3 pin header in the H4 header pattern on the board, cutting the two links on the back of the board as shown near the upper right of Figure A4-1, and then adding two jumpers to select the stepping mode. For example, if both center pins are connected to the bottom pins of H4, eighth-step operation will result. While the total current drawn from the wall transformer for full stepping is constant, for any of the other modes, the total current varies with the step position, between a maximum equal to that found for full stepping and a minimum equal to 0.707 times that value. The pulse-width-modulation (PWM) control circuit defaults to the nominal RC values suggested by Allegro in the data sheet for this driver chip. Each motor winding is subjected to a current that alternates between ramping up and decaying down. When one of the winding currents is low, the 12 V power supply voltage is applied across the winding until the voltage across the current-sensing resistor crosses its threshold voltage. At that point, the power supply voltage is cut off from the motor winding. The winding current decays for a time determined by C2R3 (for one winding) or C6R8 (for the other one). The rate of decay is determined by the voltage on the PFD pin of Figure A4-1. With this pin voltage defaulting to 3.3 V, the current decays relatively slowly, with minimum current ripple. The maximum stepping rate is evidently achieved with the fast decay setting of 0 V on the PFD pin, albeit with increased audible noise and vibration. In contrast to the Qwik&Low board, the stepper-motor board is a simpler board to build. Figure A4-3 shows the front and back artwork. Even though the board employs size 1206 surface-mount resistors and capacitors, these are relatively large (for surfacemount parts). The resistors are stamped with their resistor values, a help for checking the board. Check with www.qwikandlow.com/stepper_support/ for information on obtaining the bare stepper-motor controller board, parts, and a stepper motor.
Cost Each
Unpopulated
Quantity
Distributor
Part Number Digi-Key Digi-Key Digi-Key Digi-Key C2,6 C3,4 C5,9 C1,7,8 1206 1206 1206 0.25 Dia. Stepper motor driver board 0.001uF 50V X7R ceramic capacitor 0.01uF 50V X7R ceramic capacitor 0.1uF 50V X7R ceramic capacitor 33uF 35V electrolytic capacitor PCBCART Yageo Yageo Yageo Panasonic 4.50 0.09 0.09 0.10 0.15
Designation
Footprint
Part Description
Manufacturer
1 2 2 2 3
x x x x x
2 1 4 2 2 0 1 2 2 1 1 2 1 1 1 1 DO-41 SOT-23 DPAK SOIC-24 0.57x0.36 0.57x0.36 Digi-Key Digi-Key Digi-Key Digi-Key U1 TB1,2 CON1 CON1X Motor driver, A3967SLB-T 4-conductor terminal block, right angle 2.1mm barrel connector for power [email protected] regulated wall wart
311-1.00KFRCT 311-10.0KFRCT 311-51.1KFRCT P1.5ASCT P1.8ASCT P1.2ASCT 490-2839 490-2834 S2011E-36 S1011E-36 HRP10H HKC10H AE10G-5 1N5818 BAT54C-FDICT MC33269DTRK-3OSCT
Digi-Key Digi-Key Digi-Key Digi-Key Digi-Key Digi-Key Digi-Key Digi-Key Digi-Key Digi-Key Digi-Key Digi-Key Digi-Key Digi-Key Digi-Key Digi-Key
R10,11 R9 R1,2,3,8 R4,6 R5,7 R5,7 POT3 POT1,2 H2,4 H1 H3 H3X H3Y D1 D2 U2
1206 1206 1206 1210 1210 1210 0.26x0.27 0.26x0.27 3x2 pin header 7x1 pin header
1.00K 1/4W 1% thick film resistor 10.0K 1/4W 1% thick film resistor 51.1K 1/4W 1% thick film resistor 1.5 ohm 1.2W 5% thick film resistor 1.8 ohm 1.2W 5% thick film resistor 1.2 ohm 1.2W 5% thick film resistor 20K 1 turn, side adjust trimpot 100K 1 turn, side adjust trimpot Male straight 2 row x 3 pin header Male straight 7x1 header with 2,4,6 removed Male shrouded 100 mil header, 5x2 pins Mating female ribbon cable connector 10-conductor ribbon cable, 5 feet 30V,1A Schottky rectifier Dual Schottky diode 3.3V voltage regulator
Yageo Yageo Yageo Panasonic Panasonic Panasonic Murata Murata Sullins ($2.74/72pins) Sullins ($1.51/36pins) Assmann Elect. Assmann Elect. Assmann Elect. Vishay Diodes Inc. ON Semi.
0.09 0.09 0.09 0.36 0.36 0.36 0.79 0.79 0.23 0.30 0.59 0.50 0.90 0.23 0.60 1.00 Allegro 2.75 Phoenix 0.90 CUI Stack 0.38 CUI Inc. 5.38 Stepper driver board + power = Applied Motion Dialight E-Switch 4.79 2.70 3.45 10.00 3M Keystone Building Fasteners Building Fasteners 0.52 0.80 0.08 0.08 0.02 0.18 0.02 0.02 Parts for motor and stand = Total =
0.18 0.09 0.36 0.72 0.72 0.00 0.79 1.58 0.46 0.30 0.59 1.00 0.90 0.23 0.60 1.00 2.75 1.80 0.38 5.38 20.84 4.79 2.70 3.45 10.00 2.08 3.20 0.32 0.32 0.08 0.36 0.04 0.04 27.38 48.22
1 2 1 1
163395
Jameco
TB1X
Bipolar stepper motor, 8.4V, 280mA/phase Panel-mounted blue LED SPDT toggle switch Aluminum stand Four rubber feet for aluminum stand Round spacer for 4-40 screw, 1/8 long 4-40 x 3/8 machine screw 4-40 hex nut Internal tooth lock washers, 4 Socket head screws, 6-32 x 1-1/2 Internal tooth lock washers, 6 Hex machine screw nuts, 6-32
1 1 1 Mouser Digi-Key Digi-Key Digi-Key Bolt Depot Bolt Depot Bolt Depot Bolt Depot 1/4 round
350-1592 EG2446
4 4 4 4 4 2 2 2
Total Cost
303
304
TB1
1 2 3 4
TB2
1 2 3 4 2 3 1
H1
2 1 1
Ground
6 4 2 5 3 1 8 8
10
Vm U1
1 1 24 2 2 1 2 2 3 22 1 1 2 23 4 1 2 21 2 5 1 6 19 1
R3 C2 C1 +
20
R8 C6 C7 + D1 CON1
7 3 8 17 2 2
18
16
1 11 1 2 1 12 13 2 14
10
15
4 4
R2 R1 C5 U2 H2
2 1 2 1 1 2
1 3 4
8 2 3 1 2 1 2 1 1 2
R5 C3 R4 POT1
2 4 6
B R S S
1
R B W Y
R9 Rev 2
Stepper Driver
3 1
C9
2 1
Ground
5 1 3 5 7
C8
H3
INDEX
AD22103 temperature sensor, 127 AD5601 DAC, 246 ADC (analog-to-digital converter), 119 ADT7301 temperature sensor, 251 Analog vs. digital I/O pins, 54 ASCII and ASCIID functions, 79 ASCII conversion of a hex digit, 290 ASCII4 and ASCII4D functions, 81 Assembly language instruction set, 280 Average current intermittent sleep mode, 23 example (table), 26 Awakening vs. interrupt vectoring, 229 BDM (Background Debug Mode) vector, 17 address, 32 pin, 257 Bit manipulations in C, 50 Breakout timer, 229 Brownout reset module (figure), 49 C programming language use, 28 Calibrate.c, 176 CCP1 and CCP2 modules, 181 capture mode (figure), 182 C18 utility, 56, 150 Coin cell (CR2032) aging, 37 characteristics, 19 Compilation of C code, 56 Configuration selections, 47 without QwikBug, 17 CPU registers, 288 Crystal oscillator, 38 schematic, 294 DAC (digital-to-analog converter), 246 Delay macro, 51 Digital inputs vs. outputs, 54 Direct addressing of CPU, 282 Display function, 68 Display strings, 61 Displayable characters, 62 DMM, 31 DS2415 1-wire time chip, 215 Editor, Crimson, 57 EEPROM, 187 EEtest.c, 193
Electronix Express (www.elexp.com), 31 Expansion header, 40 4PDT switch, 221 Fosc/4 test point, 33 Global variables vs. local variables, 50, 51 Gray code, 134 Harvard architecture, 278 iButton, 213 Idle mode current (table), 27 and interrupts, 165 I2C bus, 241 Indirect addressing of CPU of program memory, 289 of RAM variables, 286 Interrupts, 89, 159 high- and low-priority, 89 effect of separate handlers, 163 sources (table), 166 INTOSC internal oscillator, 21 block diagram, 22 calibration, 173 INTRC low-power internal oscillator, 21 block diagram, 22 effect on run/idle mode current, 27 Keypad interface, 297 LCD (liquid crystal display), 38, 219 controller, 15, 58, 219 controller test points, 223 multiplexed waveforms, 223 schematic, 222, 294 SPI interface, 59 starburst segment coding, 221 LCD.c, 233 Learning curve, 14 Local vs. global variables, 50, 51 Loop time via Timer1, 91 via watchdog timer, 55 Lst files, 277 MCU (microcontroller unit) block diagram, 16 clock rate decision, 53
306
Index
307
current draw vs. frequency (chart), 23 pin use (table), 41 schematic, 294 Measure.c, 82 Measurement interrupt duration, 155 useful work in main loop, 156 Microchip Technology Steve Sanghi, 13 5 billion microcontrollers, 15 C18 compiler, 17, 57 nanoWatt Technology, 19 MICRODESIGNS, 31 Monotonic DAC output, 247 Motorola, 15 Nop macro, 56 Number-to-ASCII conversion, 78 1-wire interface, 201 for multiple devices, 213 Open-drain output, 201 Oscilloscope for loop time measurement, 52 for time interval measurement, 153 persist mode, 154 OSCTUNE register, 169 Parasitic power, 202 PIC18LF6390, 219 PIC18LFxxxx family features, 20 PICkit 2 programmer, 17, 29, 32, 258 Pipelined operation of CPU, 278 Power-on resetting of MCU and LCD controller, 68 Pushbutton debouncing, 25 function, 67, 69 PV.c, performance verification program, 299 Qwik&Low board, cover, 27, 32, 293, 298 parts list, 296 schematic, 294 QwikBug, 17, 256 console window, 71 QwikProgram 2 utility, 17, 32, 258 RPG (rotary pulse generator), 133 interrupt-driven RPG.c, 142 polled RPG function, 137
RPGcounter function for interrupt-driven RPG, 141 for polled operation, 138 Serial test port, 32 signal inversion, 32 USB-to-serial adapter, 33 SFRs (special-function registers) 8-bit parts of 16-bit SFRs, 53 Shadow registers, 162 Sleep plus Nop macro combination, 56 Special Function Register addresses, 291 SPI (Serial Peripheral Interface), 58, 241 MOSI/MISO terminology, 249 SSN (silicon serial number), 201 SSN.c, 206 Start and Stop macros, 154 Start, Stop, and Send functions, 183 in Measure.c, 85 Stepper motor, 107 stepping code, 115 Stepper motor driver board, 113, 300, 304 parts list, 303 schematic, 114, 301 Switch debouncing, 25 T1.c template, 44 I/O connections, 55 T2.c template, 63 T3.c template, 98 Temperature sensor with SPI output, 251 with voltage output, 127 TenthsFahr.c template, 132 Thermochron parts, 213 Time function, 66, 69 Timer0, 170 Timer1, 94 Timer1 oscillator, 91 Timer3, 96 Totem-pole output, 202 TRIS registers for input/output choice, 54 TXascii macro, 78 UART, 72 Watchdog timer, 24 www.qwikandlow.com, 17, 32, 258