103 10
English Pages 364 [356] Year 2023
Ahmet Bindal
Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers
Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers
Ahmet Bindal
Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers
Ahmet Bindal Computer Engineering Department San Jose State University San Jose, CA, USA
ISBN 978-3-031-27840-2 ISBN 978-3-031-27841-9 https://doi.org/10.1007/978-3-031-27841-9
(eBook)
© The Editor(s) (if applicable) and The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 This work is subject to copyright. All rights are reserved by the Publisher, whether the whole or part of the material is concerned, specifically the rights of translation, reprinting, reuse of illustrations, recitation, broadcasting, reproduction on microfilms or in any other physical way, and transmission or information storage and retrieval, electronic adaptation, computer software, or by similar or dissimilar methodology now known or hereafter developed. The use of general descriptive names, registered names, trademarks, service marks, etc. in this publication does not imply, even in the absence of a specific statement, that such names are exempt from the relevant protective laws and regulations and therefore free for general use. The publisher, the authors, and the editors are safe to assume that the advice and information in this book are believed to be true and accurate at the date of publication. Neither the publisher nor the authors or the editors give a warranty, expressed or implied, with respect to the material contained herein or for any errors or omissions that may have been made. The publisher remains neutral with regard to jurisdictional claims in published maps and institutional affiliations. This Springer imprint is published by the registered company Springer Nature Switzerland AG The registered company address is: Gewerbestrasse 11, 6330 Cham, Switzerland
For my father who always tried to show me the right path
Preface
This book is written for senior-level undergraduate students and young engineers to capture the basics of register-based, embedded microcontroller programming specifically to develop robotics-related projects. The manuscript mostly concentrates on robotic interfaces and supporting firmware that enable the reader to integrate these programs into building an autonomous robot. Every interface program is explained in detail, including how the peripheral hardware works, how to configure and set up the device registers in a 16-bit Microchip microcontroller, and how to program the peripheral registers to achieve a working interface. Although this book is written for the 16-bit Microchip processors, specifically for dspic33fj128mc802/804, the principles can also be applied to all 8-bit and 32-bit Microchip microcontrollers. Chapter 1 covers how to configure the oscillator registers. Chapter 2 configures the parallel port of the processor, and how to set up the input and output ports, using the Peripheral Pin Select (PPS) features of the processor. Chapter 3 is about the serial interface, but mainly focuses on the SPI interface of dspic33fj128mc802/804 since this interface is so much faster compared to I2C. Chapter 4 covers the interfaces with external memories, SRAM and Flash. External memories become indispensable for any design when the 16 KB internal data memory becomes full, and the application requires extra memory for large-scale data transfer to produce a result. Chapter 5 covers data converters, the native Analog-to-Digital Converter (ADC), and Digitalto-Analog Converter (DAC) which is not a native to some 16-bit microcontrollers. Chapter 6 goes through primary sensors for a mobile robot. How a robot maintains stability, how to achieve cognitive recognition of objects are covered in this chapter. Chapter 7 studies the devices required for robotic actuation such as servos, brushless
vii
viii
Preface
and stepper motors, and their operation. PWM signal generation with timers, the PWM1 and PWM2 motor control units, and the Output Compare unit are discussed in this chapter in great extent with working programming examples. There is also a short appendix at the end of the book that shows how to build the front-end electronics between a sensor and the ADC, using operational amplifiers, in case the reader ventures to design one. San Jose, CA, USA
Ahmet Bindal
Contents
1
Oscillator Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1 23
2
Input/Output Ports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1 dspic33fj128mc802/804 Architecture . . . . . . . . . . . . . . . . . . . . . . 2.2 dspic33fj128mc802 Input/Output . . . . . . . . . . . . . . . . . . . . . . . . . 2.3 dspic33fj128mc802 Peripheral Pin Select (PPS) System . . . . . . . . 2.4 dspic33fj128mc802 Physical I/O Pin Descriptions . . . . . . . . . . . . 2.5 TRIS, PORT and LAT Registers in dspic33fjmc802 . . . . . . . . . . . 2.6 Parallel Port Examples with dspic33fj128mc802 . . . . . . . . . . . . . . 2.7 dspic33fj128mc804 Input/Output . . . . . . . . . . . . . . . . . . . . . . . . . References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25 25 26 28 35 38 42 52 55
3
Serial Port . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1 Introduction to Serial Peripheral Interface (SPI) . . . . . . . . . . . . . . 3.2 Introduction to Inter Integrated Circuit (I2C) . . . . . . . . . . . . . . . . 3.3 SPI interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57 57 61 65 75
4
External Memories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1 Synchronous Random Access Memory (SRAM) . . . . . . . . . . . . 4.2 Flash Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
Data Converters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1 Introduction to Analog-to-Digital Converter (ADC) . . . . . . . . . . 5.2 Flash ADC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Ramp ADC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.4 Successive Approximation ADC . . . . . . . . . . . . . . . . . . . . . . . . 5.5 Analog-to-Digital Converter in dspic33fj128mc802 . . . . . . . . . .
. . . .
. 77 . 77 . 91 . 109 . . . . . .
111 112 115 117 119 120
ix
x
Contents
5.6 Sample ADC Projects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.7 Introduction to Digital-to-Analog Converter (DAC) . . . . . . . . . . 5.8 Digital-to-Analog Converter in dspic33fj128mc804 . . . . . . . . . . References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . .
149 153 153 170
6
Peripherals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1 Accelerometer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Pixy Camera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3 Microphone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . .
171 171 207 249 265
7
Output Devices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1 Timers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2 PWM1 Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.3 PWM2 Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4 PWM Registers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.5 Sample Programs for PWM Operation . . . . . . . . . . . . . . . . . . . . . 7.6 Output Compare Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.7 Servo Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.8 Stepper Motors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.9 Brushless DC Motors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
267 267 270 274 275 283 293 304 305 313 317
Appendix Designing Front-End Electronics . . . . . . . . . . . . . . . . . . . . . . 319 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351
About the Author
Ahmet Bindal received his M.S. and Ph.D. degrees in Electrical Engineering from the University of California, Los Angeles CA. His doctoral research was the material characterization of HEMT GaAs transistors. During his graduate studies, he was a research associate and a technical consultant for Hughes Aircraft Co. In 1988, he joined the technical staff of IBM Research and Development Center in Fishkill, NY where he worked as a device design and characterization engineer. He developed asymmetrical MOS transistors and ultra thin Silicon-On-Insulator (SOI) technologies for IBM. In 1993, he transferred to IBM at Rochester, MN, as a senior circuit design engineer to work on the floatingpoint unit of AS-400 main frame processor. He continued his circuit design career at Intel Corporation in Santa Clara, CA, where he designed 16-bit packed multipliers and adders for the MMX unit in Pentium II processors. In 1996, he joined Philips Semiconductors in Sunnyvale, CA, where he was involved in the designs of instruction/data caches and various SRAM modules for the Trimedia processor. His involvement with VLSI architecture also started in Philips Semiconductors and led to the design of the Video-Out unit for the same processor. In 1998, he joined Cadence Design Systems as a VLSI architect and directed a team of engineers to design a self-timed asynchronous processor. After approximately 20 years of industry work, he joined the Computer Engineering faculty at San Jose State University in 2002. His current research interests range from nano-scale electron devices to robotics. Dr. Bindal has xi
xii
About the Author
over 30 scientific journal and conference publications and 10 invention disclosures with IBM. He currently holds three U.S. patents with IBM and one with Intel Corporation. On the light side of things, Dr. Bindal is a model aircraft builder, violin maker, painter and an avid windsurfer for more than 30 years.
Chapter 1
Oscillator Configuration
The dspic33fj microcontroller is equipped with five different clock sources [1]: primary and secondary oscillators that receive external crystal inputs, an internal low frequency RC oscillator at about 32.77KHz, an internal high frequency oscillator whose center frequency is about 7.37MHz, and an auxiliary clock source used specifically for the Digital-to-Analog Converter (DAC). The primary oscillator operates with an external crystal connected between OSC1 and OSC2 pins of the microcontroller as shown in Fig. 1.1. OSC1
C R
1MΩ C
external
POSCMD[1:0]
OSC2
internal
Fig. 1.1 External primary oscillator configuration (an external 1MΩ resistor is added between OSC1 and OSC2 pins if oscillator is used in XT and HS modes)
This configuration allows three different crystal frequencies to be used. If the crystal frequency is between 3MHz and 10MHz, the crystal (XT) mode of operation is initiated. High speed crystals in the range of 10MHz and 40MHz use high speed (HS) mode of operation. Both the XT and HS modes use 1MΩ external resistor between OSC1 and OSC2 pins. The third mode uses an external clock (EC) which
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 A. Bindal, Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers, https://doi.org/10.1007/978-3-031-27841-9_1
1
2
1
Oscillator Configuration
allows crystal frequencies up to 64MHz. In each mode, the oscillator registers should be programmed to support a specific crystal frequency mode. The secondary oscillator source is a low frequency RC oscillator for low power applications and uses a crystal centered at 32.77KHz connected between SOSCO and SOSCI terminals of the microcontroller as shown in Fig. 1.2. SOSCO
C R C
LPOSCEN
SOSCI
external
internal
Fig. 1.2 External secondary oscillator configuration
The third oscillator is an internal, low frequency RC oscillator at 32.77KHz for low power applications (LPRC). This oscillator is also used for Watch Dog Timer (WDT) and Fail-Safe Clock Monitor (FSCM). The fourth oscillator is an internal fast RC (FRC) oscillator mainly used for the CPU and the peripherals if the user chooses not to use an external crystal. Although the oscillator frequency is centered at 7.37MHz, it can be decreased to 28.79KHz by a clock divider (FRCDIV) or increased to a much higher frequency (approximately 80MHz) using the microcontroller’s native Phase-Lock-Loop (PLL) unit. The fifth clock source is specifically designed for the Digital-to-Analog Converter (DAC). It either uses an external auxiliary clock source between 3.5MHz and 10MHz or the output of the primary oscillator (POSCCLK) or the output of the PLL depending on the need. A simplified version of clock sources for dspic33fj processors is shown in Fig. 1.3. The 8-1 MUX allows all possible combinations of external primary and secondary clock sources and internal FRC and LPRC oscillators to be used for the CPU and the peripherals. According to configuration of the oscillator registers it is possible to generate clock frequencies from 28.79KHz to approximately 80MHz at FOSC, FP and FCY terminals.
1
Oscillator Configuration
3
To be able to use any one of the five clock sources in Fig. 1.3, all 16-bit oscillator registers must be programmed accordingly.
XT, HS, EC
POSCCLK
Primary Oscillator
S3
POSCMD[1:0]
FIN
PLLOUT
PLL
DOZE
S2
S1/S3
S1
FCY (to CPU)
PLLDIV PLLPRE PLLPOST DOZE[2:0] FP (to peripherals)
FRCDIV
FRC
FRC Oscillator
FRCDIVN
S7
FOSC/2
FOSC
TUN[5:0] FRCDIV[2:0] FRCDIV16
FRC/16
S6
FRC
LPRC Oscillator
LPRC
Secondary Oscillator
SOSC
S0
S5
S4
LPOSCEN ClkFail
S7
ClkSwitch
Reset
NOSC[2:0]
FNOSC[2:0] WDT, FSCM
TIMER1
0 1 Auxillary Oscillator
AUXCLK
AUXSELCLK
FOUT
FA
Divide by A
1
DAC
0 APSTSCLR[2:0]
SELACLK ASRCSEL AOSCMD[1:0]
Fig. 1.3 External and internal clock sources for dspic33fj microcontrollers
The first oscillator control register (OSCCON) selects between the external and internal oscillators as clock sources for the CPU and the peripherals. The description of this register is shown in Fig. 1.4. 15 14 13 12 11 10 9 -
8
-
COSC[2:0]
7
6
5
4
3
2
1
0
CLKLOCK
IOLOCK
LOCK
-
CF
-
LPOSCEN
OSWEN
NOSC[2:0]
Fig. 1.4 Oscillator control register (OSCCON)
4
1
Oscillator Configuration
In this register, the OSWEN bit requests to switch from the current oscillator, COSC[2:0], to a new oscillator, NOSC[2:0] if the clock switch feature is enabled for the microcontroller. The LPOSCEN bit is an enable entry to activate the secondary low power oscillator. The CF bit indicates clock failure detection, and it is a readonly bit. The LOCK bit indicates the status of the PLL, whether it is locked at a certain frequency or still trying to stabilize the oscillation. The IOLOCK bit locks the Peripheral Pin Select (PPS) feature, and prohibits reprogramming the PPS registers. The PPS system maps a multitude of analog, digital and peripheral Input/Output (I/O) pins of the microcontroller onto a single physical pin, and it is extremely useful in configuring external pins to receive specific inputs and produce specific outputs. The CLKLOCK pin locks the system clock and disables the clock switch feature. The COSC[2:0] bits indicate the current clock in use, and it is a read-only entry. The NOSC[2:0] correspond to the next clock source to be used when clock switching feature is enabled. Table 1.1 lists the current clock source, the next clock source and the port names of the 8-1 MUX in Fig. 1.3. Table 1.1 COSC[2:0] and NOSC[2:0] selection guide in OSCCON register COSC[2:0] and NOSC[2:0] entries in OSCCON register 111 = Select port S7 - FRC oscillator with divide-N 110 = Select port S6 - FRC oscillator with divide-16 101 = Select port S5 - LPRC oscillator only 100 = Select port S4 - Secondary oscillator only 011 = Select port S3 - Primary oscillator with PLL 010 = Select port S2 - Primary oscillator only 001 = Select port S1 - FRC oscillator with divide-N and PLL 000 = Select port S0 - FRC oscillator only
The second oscillator register (CLKDIV) is the clock division register. This register determines the FCY clock frequency for the CPU, the FOSC clock frequency for the peripherals, and adjusts the PLL frequency as shown in Fig. 1.3. The bit field format of this register is given in Fig. 1.5.
1
Oscillator Configuration
5
15 14 13 12 ROI
11
10 9
8
7
6
DOZEN
5
4
3
2
1
0
-
DOZE[2:0]
PLLPOST[1:0]
PLLPRE[4:0]
FRCDIV[2:0] Fig. 1.5 Oscillator clock division register (CLKDIV)
The PLLPRE[4:0] bits sequentially divide the input of the PLL by a factor of two to a factor of 33 as shown in Table 1.2. Table 1.2 PLLPRE[2:0] selection guide in CLKDIV register PLLPRE[2:0] entry in CLKDIV register 11111 = PLL input (FIN) / 33 11110 = PLL input (FIN) / 32 ... 00010 = PLL input (FIN) / 4 00001 = PLL input (FIN) / 3 00000 = PLL input (FIN) / 2
The PLLPOST[1:0], on the other hand, divides the output of the Voltage Controlled Oscillator (VCO) of the PLL by a factor of two to a factor of eight as shown in Table 1.3. Table 1.3 PLLPOST[1:0] selection guide in CLKDIV register PLLPOST[1:0] entry in CLKDIV register 11 = VCO output / 8 10 = none 01 = VCO output / 4 (default) 00 = VCO output / 2
6
1
Oscillator Configuration
The FRCDIV[2:0] bits divides the FRC oscillator frequency by a factor of 2N where N is between zero and eight. The selection guide of this entry is shown in Table 1.4. Table 1.4 FRCDIV[2:0] selection guide in CLKDIV register FRCDIV[2:0] entry in CLKDIV register 111 = FRC / 256 110 = FRC / 64 101 = FRC / 32 100 = FRC / 16 011 = FRC / 8 010 = FRC / 4 001 = FRC / 2 000 = FRC (default)
The DOZEN bit enables the unit responsible of dividing the CPU frequency. If disabled, the ratio between the processor and peripheral clock frequencies become equal to each other. The DOZE[2:0] bits reduces the CPU clock frequency, FCY, by a factor of 2N where N changes between zero and seven as shown in Table 1.5. Table 1.5 DOZE[2:0] selection guide in CLKDIV register DOZE[2:0] entry in CLKDIV register 111 = FP / 128 110 = FP / 64 101 = FP / 32 100 = FP / 16 011 = FP / 8 (default) 010 = FP / 4 001 = FP / 2 000 = FP
1
Oscillator Configuration
7
The Recover-On-Interrupt (ROI) bit causes interrupts to clear the DOZEN bit. If disabled, however, interrupts will have no effect on the DOZEN bit. The third oscillator register (PLLFBD) is a PLL feedback divisor register whose bit field format is shown in Fig. 1.6. Fig. 1.6 PLL feedback divisor register (PLLFBD)
15 14 13 12 11 10 9 -
-
-
-
-
-
8
7
6
5
4
3
2
1
0
-
PLLDIV[8:0]
The PLLDIV[8:0] bits range from a factor of two to a factor of 513 as shown in Table 1.6. Table 1.6 PLLDIV[8:0] selection guide in PLLFBD register PLLDIV[8:0] entry in PLLFBD register 111111111 = 513 x VCO input 111111110 = 512 x VCO input ... 000110000 = 50 x VCO input ... 000000010 = 4 x VCO input 000000001 = 3 x VCO input 000000000 = 2 x VCO input
The fourth oscillator register (OSCTUN) shown in Fig. 1.7 tunes the FRC center frequency by approximately 12% either in positive or in negative directions as shown in Table 1.7. Fig. 1.7 FRC oscillator tuning register (OSCTUN)
15 14 13 12 11 10 9 -
-
-
-
-
-
-
8
7
6
-
-
-
5
4
3
2
TUN[5:0]
1
0
8
1
Oscillator Configuration
Table 1.7 TUNE[5:0] selection guide in OSCTUN register TUN[5:0] entry in OSCTUN register 111111 = FRC center – 0.375% (7.345MHz) ... 100001 = FRC center – 11.625% (6.520MHz) 100000 = FRC center – 12% (6.490MHz) 011111 = FRC center + 11.625% (8.230MHz) 011110 = FRC center + 11.250% (8.200MHz) … 000001 = FRC center + 0.375% (7.400MHz) 000000 = FRC center (7.370MHz)
The last oscillator register (ACLKCON) is the auxiliary clock divisor control register shown in Fig. 1.8. This register basically adjusts the clock frequency to be used in the Digital-to Analog Converter (DAC) unit. 15 14 -
-
13 SELACLK
12 11 10 9
7
6
5
4
3
2
1
0
ASRCSEL
-
-
-
-
-
-
-
8
AOSCMD[1:0] APSTSCLR[2:0] Fig. 1.8 Auxiliary clock divisor control register (ACLKCON)
The ASRCSEL bit defines if the primary oscillator is the auxiliary clock source. The auxiliary post scalar entry, APSTSCLR[2:0], divides the auxiliary clock by a factor of 2N where N ranges between zero and eight as shown in Table 1.8.
1
Oscillator Configuration
9
Table 1.8 APSTSCLR[2:0] selection guide in ACLKCON register APSTSCLR[2:0] entry in ACLKCON register 111 = FOUT / 1 110 = FOUT / 2 101 = FOUT / 4 100 = FOUT / 8 011 = FOUT / 16 010 = FOUT / 32 001 = FOUT / 64 000 = FOUT / 256
The AOSCMD[1:0] bits define which primary oscillator is used as the auxiliary clock source as shown in Table 1.9. Table 1.9 AOSCMD[1:0] selection guide in ACLKCON register AOSCMD[1:0] entry in ACLKCON register 11 = EC primary oscillator 10 = XT primary oscillator 01 = HS primary oscillator 00 = Auxiliary oscillator is disabled
The SELACLK bit defines the auxiliary oscillators to be the source clock for auxiliary clock divider. Besides the main oscillator registers, there are two configuration registers that must be programmed to select the available oscillators in Fig. 1.3 during system boot. The first oscillator configuration register is an 8-bit oscillator frequency select register (FOSCSEL) used during the system start-up as shown in Fig. 1.9. This register basically activates one of the eight possible oscillator configurations. The FNOSC[2:0] bits in this register either select one of the internal FRC or LPRC oscillators, or one of the external primary or the secondary oscillators according to Table 1.10. Once the start-up phase is complete, the user may switch from this initial oscillator to a different oscillator in software. The Internal-to-External-Switch-Over (IESO) bit in the FOSCSEL register enables the startup with an internal oscillator then switches over to a user-defined external oscillator.
10 Fig. 1.9 Initial oscillator select register (FOSCSEL)
1
Oscillator Configuration
7
6
5
4
3
IESO
-
-
-
-
2
1
0
FNOSC[2:0]
Table 1.10 FNOSC[2:0] selection guide FNOSC[2:0] entry in FOSCSEL register 111 = Select port S7 - FRC oscillator with divide-N 110 = Select port S6 - FRC oscillator with divide-16 101 = Select port S5 - LPRC oscillator 100 = Select port S4 - Secondary oscillator 011 = Select port S3 - Primary oscillator with PLL 010 = Select port S2 - Primary oscillator 001 = Select port S1 - FRC oscillator with PLL 000 = Select port S0 - FRC oscillator
The second 8-bit oscillator configuration register (FOSC) basically enables clock switching feature, the type of the primary oscillator used for the clock, and allows single or multiple PPS configurations as shown in Fig. 1.10. Fig. 1.10 Oscillator configuration register (FOSC)
7
6
5
4
3
2
IOL1WAY
-
-
OSCIOFNC
FCKSM[1:0]
1
0
POSCMD[1:0]
In this register, the POSCMD[1:0] bits select one of the primary oscillator modes according to Table 1.11. Each primary oscillator in this table requires a crystal that operates in a different frequency range as mentioned earlier. Table 1.11 POSCMD[1:0] selection guide POSCMD[1:0] entry in FOSC register 11 = Primary oscillator is disabled. 10 = Primary oscillator is in HS mode. 01 = Primary oscillator is in XT mode. 00 = Primary oscillator is in EC mode.
1
Oscillator Configuration
11
The OSCIOFNC bit qualifies the OSC2 pin of the microcontroller to be used as a clock output. Otherwise, this pin is used as a general purpose I/O pin. Finally, the FCKSM[1:0] bits enable the clock-switch mode and fail-safe clock monitoring features in the microcontroller as shown in Table 1.12. A clock monitoring system constantly observes the changes in the external clock, and substitutes this clock with an internal clock in case the external clock fails. Clock switching among the primary, secondary and FRC clocks is enabled anytime by programming the OSCCON register. In order to transition from one clock to another, the first step is to observe the COSC[2:0] bits to find out the current clock type. The second step is to define the desired clock source by programming the NOSC[2:0] bits and assert CLKLOCK = 0 in order to unlock the clock switch-over option. The third step is to turn on the clock switch bit by OSWEN = 1. This programming sequence initiates a system task that starts with comparing the COSC[2:0] and NOSC[2:0] entries. If they are equal, clock switching is aborted, and OSWEN is cleared. Otherwise, the LOCK and CF bits are cleared by the system, and the new oscillator is turned on. If the new clock requires the PLL the system waits until the PLL locks by LOCK = 1. Oscillator switch-over becomes complete when the OSWEN bit is cleared. Table 1.12 FCKSM[1:0] selection guide FCKSM[1:0] entry in FOSC register 1X = Clock switching is disabled. Fail safe clock monitoring disabled. 01 = Clock switching is enabled. Fail safe clock monitoring disabled. 00 = Clock switching is enabled. Fail safe clock monitoring enabled.
The first example in Program 1.1 configures the microcontroller to operate with a single internal FRC clock source. Program 1.1 // This program defines a single FRC oscillator as a clock source at 7.37MHz #include // Configuration Register - FOSCSEL #pragma config IESO = 0 #pragma config FNOSC = 7
// internal to external clock switch-over is disabled // select FRC oscillator with Divide-N option at reset
12
1
Oscillator Configuration
// Configuration Register - FOSC #pragma config FCKSM = 3
// both clock switching and fail-safe modes are disabled
#pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled int main () { // OSCCON register is not applicable because there is no 2-step clock definition // OSCTUN Register OSCTUNbits.TUN = 0;
// select FRC = 7.37MHz
// CLKDIV Register CLKDIVbits.FRCDIV = 6; // FRCDIVN = FRC/64 = 7.37/64 = 115.15KHz = FOSC CLKDIVbits.DOZE = 1;
// FOSC/4 = 115.15/4 = 28.8KHz = FCY
CLKDIVbits.DOZEN = 1; // DOZE defines the ratio between cpu and peripheral clocks // Other entries in CLKDIV register is not applicable because there is no PLL }
All configuration statements start with the “#pragma config” heading in Fig. 1.11. The first configuration section programs the FOSCSEL register. The IESO = 0 entry disables any internal to external oscillator switch-over feature in the microcontroller because there is only one FRC clock source. FNOSC = 7 selects a single FRC oscillator. // This program defines an FRC oscillator as a clock source at 7.37MHz #include // Configuration Register - FOSCSEL #pragma config IESO = 0 // internal to external clock switch-over is disabled #pragma config FNOSC = 7 // select FRC oscillator with Divide-N option at reset // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled int main () { … }
Fig. 1.11 Configuring the clock source to be a single FRC oscillator
1
Oscillator Configuration
13
The second configuration section programs the FOSC register. FCKSM = 3 disables both the clock switching and fail-safe clock monitoring features because no external clock source is used. OSCIOFNC = 0 defines OSC2 (or OSCO) pin as a digital I/O pin, but not a clock output. POSCMD = 3 disables all primary HS, XT and EC type oscillators. After the oscillator configuration is complete, the main section of the program selects the internal FRC oscillator to be the only clock source as shown in Fig. 1.12. The entries in the OSCCON register are irrelevant in generating a single FRC clock because this register only describes a two-step clock process. OSCTUNbits. TUN = 0 in the OSCTUN register selects 7.37MHz to be the clock frequency for the FRC oscillator. CLKDIVbits.FRCDIV = 6 in the same register divides the FRC frequency by 64, and produces FOSC to be 7.37MHz/64 = 115.15KHz. CLKDIVbits.DOZE = 1 divides FP by two (FP = FOSC/2) and determines the CPU clock frequency, FCY, to be FOSC/4 = 28.8KHz according to Fig. 1.3. CLKDIVbits.DOZEN = 1 enables the DOZE[2:0] entry. No other entries are needed in the CLKDIV register because PLL is inactive according to this program. // This program defines a single FRC oscillator as a clock source at 7.37MHz #include … int main () { // OSCCON register is not applicable because there is no 2-step clock definition // OSCTUN Register OSCTUNbits.TUN = 0;
// select FRC = 7.37MHz
// CLKDIV Register CLKDIVbits.FRCDIV = 6; // FRCDIVN = FRC/64 = 7.37/64 = 115.15KHz = FOSC CLKDIVbits.DOZE = 1; // FOSC/4 = 115.15/4 = 28.8KHz = FCY CLKDIVbits.DOZEN = 1; // DOZE defines the ratio between cpu and peripheral clocks // Other entries in CLKDIV register is not applicable because there is no PLL }
Fig. 1.12 Programming the single FRC oscillator to operate at 7.37MHz
The second example shows a two-step oscillator process where the system starts up with a low frequency FRC oscillator and then engages a high-speed FRC oscillator with PLL when its frequency stabilization is complete as shown in Program 1.2.
14
1
Oscillator Configuration
Program 1.2 // This program switches from a low freq. frc oscillator to a higher freq. frc with PLL #include // Configure FOSCSEL #pragma config IESO = 1 #pragma config FNOSC = 7
// two-speed oscillator start-up is enabled // initial clock at reset is FOSC=FRCDIVN
// Configure FOSC #pragma config FCKSM = 1
// clock switching is enabled but fail-safe is disabled due to no
XTAL #pragma config OSCIOFNC = 0 // OSC2 is not a clock pin but digital I/O pin due to no XTAL #pragma config POSCMD = 3
// primary oscillator option is disabled
int main () { // Initial clock source is the FRC oscillator // FRC oscillator is at 7.37MHz OSCTUNbits.TUN = 0; // Initial values of FOSC and FCY at reset CLKDIVbits.FRCDIV = 7; CLKDIVbits.DOZE = 3;
// FRCDIVN = FRC/256 = 7.37/256 = 28.8KHz = FOSC // FOSC/16 = 28.8/16 = 1.8KHz = FCY
CLKDIVbits.DOZEN = 1;
// DOZE ratio is enabled
// After switch-over, select the oscillator OSCCONbits.NOSC = 1; OSCCONbits.CLKLOCK = 0;
// the system clock will be FRC with PLL after switch-over // the system clock source can be modified
OSCCONbits.LPOSCEN = 0;
// secondary oscillator (low power oscillator) is disabled
// After switch-over, the clock source is still FRC OSCTUNbits.TUN = 0;
// FRC oscillator is at 7.37MHz
// After switch-over, determine FRCDIVN, PLL and FCY frequencies CLKDIVbits.FRCDIV = 1; // FRCDIVN (PLL input or FIN) = FRC/2 = 7.37/2 = 3.68MHz CLKDIVbits.PLLPRE = 0; PLLFBDbits.PLLDIV = 100;
// VCO input = FRCDIVN/2 = 3.68/2 = 1.84MHz // VCO output = VCO input*PLLDIV = 1.84*100 = 184MHz
CLKDIVbits.PLLPOST = 1;
// PLL output = FVCO output/4 = 184/4 = 46MHz = FOSC
CLKDIVbits.DOZE = 2; CLKDIVbits.DOZEN = 1;
// FP = 46/2 = 23MHz and FCY = FP/4 = 23/4 = 5.75MHz // DOZE ratio is enabled
// Now enable switch-over OSCCONbits.OSWEN = 1;
// oscillator switching enabled
// Wait until the oscillator switches from low freq. FRC to high freq. FRC with PLL while (OSCCONbits.COSC != 1); // Wait until the PLL locks while (OSCCONbits.LOCK != 1); }
1
Oscillator Configuration
15
Again, all configuration statements start with #pragma headings in Fig. 1.13. In the FOSCSEL register configuration, IESO = 1 indicates that a two-step oscillator start-up sequence is engaged because the system starts up with a low frequency internal FRC clock and switches over to high frequency internal FRC clock, and there is no external clock in this configuration. FNOSC = 7 indicates that the starting clock originates from the FRC oscillator, goes through the frequency divider unit, FRCDIV, and the S7 port of the 8-1 MUX in Fig. 1.3 to produce FOSC. In the FOSC register configuration, FCKSM = 1 because clock switching is enabled while fail-safe clock monitoring is disabled. Fail-safe clock monitoring needs to be engaged only when the external crystal malfunctions and the clock source needs to go back to the original FRC clock. OSCIOFNC = 0 accepts OSC2 (or OSCO) pin to be a digital I/O pin. POSCMD = 3 disables all three primary external oscillators. // This program switches from a low freq. frc oscillator to a higher freq. frc with PLL #include // Configure FOSCSEL #pragma config IESO = 1 #pragma config FNOSC = 7
// two-speed oscillator start-up is enabled // initial clock at reset is FOSC=FRCDIVN
// Configure FOSC #pragma config FCKSM = 1 // clock switching is enabled but fail-safe is disabled due to no XTAL #pragma config OSCIOFNC = 0 // OSC2 is not a clock pin but digital I/O pin due to no XTAL #pragma config POSCMD = 3 // primary oscillator option is disabled int main () { ... }
Fig. 1.13 Configuration of clock source switching from a low speed FRC oscillator to a high speed FRC oscillator with PLL
The main function programs the initial FRC oscillator frequency, the switchover process, and the PLL frequency, producing high frequency clock sources at the FOSC and FCY nodes. Since the main function is rather long, it is best to divide the program into segments and analyze each segment one at a time. The first task of the main function is to program the initial clock source: the FRC oscillator as shown in Fig. 1.14. Here, OSCTUNbits.TUN = 0 in the OSCTUN register tunes the initial clock source to be at 7.37MHz. Since the initial FRC oscillator operating at 7.37MHz is programmed to go through the FRCDIV unit to
16
1
Oscillator Configuration
achieve the lowest possible initial FRC frequency, FRCDIV[2:0] must be set to a maximum value of 256. Therefore, the CLKDIVbits.FRCDIV = 7 entry produces an FOSC = 7.37MHz/256 = 28.8KHz. If we need to supply a lower clock frequency to the CPU, the DOZE unit in Fig. 1.3 needs to be enabled by CLKDIVbits.DOZEN = 1. The CLKDIVbits.DOZE = 3 entry divides FP = FOSC/2 by eight, and produces FCY = FOSC/16 = 28.8KHz/16 = 1.8KHz for the CPU. No other entries are needed in the CLKDIV register because PLL is inactive during the start-up phase. // This program switches from a low freq. frc oscillator to a higher freq. frc with PLL #include … // Initial clock source is the FRC oscillator OSCTUNbits.TUN = 0; // FRC oscillator is at 7.37MHz // Initial values of FOSC and FCY at reset CLKDIVbits.FRCDIV = 7; // FRCDIVN = FRC/256 = 7.37/256 = 28.8KHz = FOSC CLKDIVbits.DOZE = 3; // FOSC/16 = 28.8/16 = 1.8KHz = FCY CLKDIVbits.DOZEN = 1; // DOZE ratio is enabled ... }
Fig. 1.14 Programming the initial FRC oscillator
Now, the time has come to program the clock source after the transition. For this, we need to refer to the OSCCON register as shown in Fig. 1.15. The OSCCONbits. NOSC = 1 entry makes the next oscillator to be another FRC with divide-N (the FRCDIV unit) cascaded with the PLL. The system clock should not be locked to accomplish the switch-over process; therefore, we need to assert OSCCONbits. CLKLOCK = 0. Although not important for this application, OSCCONbits. LPOSCEN = 0 disables the secondary low-power oscillator.
1
Oscillator Configuration
17
// This program switches from a low freq. frc oscillator to a higher freq. frc with PLL #include … int main () { ... // After switch-over, select the oscillator OSCCONbits.NOSC = 1; // the system clock will be FRC with PLL after switch-over OSCCONbits.CLKLOCK = 0; // the system clock source can be modified OSCCONbits.LPOSCEN = 0; // secondary oscillator is disabled … }
Fig. 1.15 Programming the new FRC oscillator features
When the switch-over occurs, the new frequency still needs to be generated from the internal FRC clock source. Therefore, the OSCTUN register comes into play one more time as shown in Fig. 1.16. By setting OSCTUNbits.TUN = 0, the new FRC clock still oscillates at 7.37MHz. The entries in the CLKDIV register must also be programmed to define the next clock source parameters. Thus, CLKDIVbits. FRCDIV = 1 divides the FRC clock by two, producing FRCDIVN = 7.37MHz/2 = 3.68MHz at the PLL input. The CLKDIVbits.PLLPRE = 0 entry divides the clock frequency at the input of the PLL by two, and generates 3.68MHz/2 = 1.84MHz going into the Voltage Controlled Oscillator (VCO). The PLL multiplies this frequency by PLLDIV[8:0] in the PLLFBD register to determine the frequency at the VCO output. Therefore, PLLFBDbits.PLLDIV = 100 produces a clock frequency of 100x1.84MHz = 184MHz at the VCO output. This value is divided inside the PLL by the PLLPOST[1:0] parameter in the CLKDIV register. Thus, CLKDIVbits.PLLPOST = 1 divides the clock frequency at the VCO output by four, and produces FOSC = 184MHz/4 = 46MHz. If the CPU clock needs to have a lower frequency than 46MHz, then the DOZE unit must be enabled by CLKDIVbits. DOZEN = 1. This unit divides the clock frequency at the FP node by four if CLKDIVbits.DOZE = 2, and produces FCY = FOSC/8 = 46MHz/8 = 5.75MHz for the CPU.
18
1
Oscillator Configuration
// This program switches from a low freq. frc oscillator to a higher freq. frc with PLL #include … // After switch-over, the clock source is still FRC OSCTUNbits.TUN = 0; // FRC oscillator is at 7.37MHz // After switch-over, determine FRCDIVN, PLL and FCY frequencies CLKDIVbits.FRCDIV = 1; // FRCDIVN (PLL input or FIN) = FRC/2 = 7.37/2 = 3.68MHz CLKDIVbits.PLLPRE = 0; // VCO input = FRCDIVN/2 = 3.68/2 = 1.84MHz PLLFBDbits.PLLDIV = 100; // VCO output = VCO input*PLLDIV = 1.84*100 = 184MHz CLKDIVbits.PLLPOST = 1; // PLL output = FVCO output/4 = 184/4 = 46MHz = FOSC CLKDIVbits.DOZE = 2; // FP = 46/2 = 23MHz and FCY = FP/4 = 23/4 = 5.75MHz CLKDIVbits.DOZEN = 1; // DOZE ratio is enabled … }
Fig. 1.16 Continuation of the program to define the new FRC oscillator and the PLL
To enable the switch-over, the OSWEN entry in the OSCCON register must be enabled by OSCCONbits.OSWEN = 1 as shown in Fig. 1.17. Now, we need to monitor the status of the switch-over process by polling the current oscillator, COSC [2:0], in the OSCCON register by a while-statement, while (OSCCONbits.COSC != 1). This statement simply waits until the current oscillator becomes an FRC oscillator with divide-N and PLL features. When the new oscillator becomes functional, then we need to wait until the PLL stabilizes by observing the LOCK parameter in the OSCCON register. The second while-statement, while (OSCCONbits.LOCK != 1) waits until LOCK = 1, indicating the PLL is fully engaged. Then, we can write the remaining application code that uses the new 5.75MHz CPU clock and the 23MHz peripheral clock.
1
Oscillator Configuration
19
// This program switches from a low freq. frc oscillator to a higher freq. frc with PLL #include ... // Now enable switch-over OSCCONbits.OSWEN = 1;
// oscillator switching enabled
// Wait until the oscillator switches to FRC with PLL while (OSCCONbits.COSC != 1); // Wait until the PLL locks while (OSCCONbits.LOCK != 1); }
Fig. 1.17 Enabling the new FRC oscillator with the PLL and starting up the new clock
Finally, the third example shows another two-step process where the system starts up with a low frequency FRC oscillator and then activates a high frequency oscillator that uses an external primary oscillator and the PLL as shown in Program 1.3. Program 1.3 // This program switches from a low freq. frc oscillator to a XT oscillator with PLL #include // Configure FOSCSEL #pragma config IESO = 1 #pragma config FNOSC = 7
// two-speed oscillator start-up is enabled // initial clock at reset is the FRC oscillator with divide N
// Configure FOSC #pragma config FCKSM = 0
// clock switch enabled, fail-safe clock monitor enabled
#pragma config OSCIOFNC = 1 // OSC2 is a clock pin #pragma config POSCMD = 1 // turn on the primary XT oscillator prior to switch-over int main () { // Initial clock source is the FRC oscillator OSCTUNbits.TUN = 0;
// FRC oscillator is at 7.37MHz
// Initial values of FOSC and FCY at reset CLKDIVbits.FRCDIV = 7; // FRCDIVN = FRC/256 = 7.37/256 = 28.8KHz = FOSC CLKDIVbits.DOZE = 3; CLKDIVbits.DOZEN = 1;
// FP = 28.8/2 = 14.4KHz and FCY = FP/8 = 14.4/8 = 1.8KHz // DOZE ratio is enabled
20
1
Oscillator Configuration
// After switch-over, select the XT oscillator OSCCONbits.NOSC = 2; // the system clock will be XT oscillator with PLL OSCCONbits.CLKLOCK = 0; // the system clock source can be modified OSCCONbits.LPOSCEN = 0; // secondary oscillator is disabled // After switch-over, the clock source is XT oscillator at 5MHz // After switch-over, determine FRCDIVN, PLL and FCY frequencies CLKDIVbits.FRCDIV = 1; // FRCDIVN(PLL input) = XT/2 = 5/2 = 2.5MHz CLKDIVbits.PLLPRE = 0; PLLFBDbits.PLLDIV = 100;
// FREF(PLL) = FRCDIVN/2 = 2.5/2 = 1.25MHz // FVCO(PLL) = FREF*PLLDIV = 1.25*100 = 125MHz
CLKDIVbits.PLLPOST = 1; CLKDIVbits.DOZE = 2;
// FRCPLL(PLL output) = FVCO/4 = 125/4 = 31.25MHz = FOSC // FCY = FRCPLL/8 = 31.25/8 = 3.9MHz
CLKDIVbits.DOZEN = 1;
// DOZE ratio is enabled
// Now enable switch-over OSCCONbits.OSWEN = 1;
// oscillator switching enabled
// Wait until the oscillator switches to XT with PLL while (OSCCONbits.COSC != 1); // Wait until the PLL locks while (OSCCONbits.LOCK != 1); }
All configuration statements start with #pragma headings in Fig. 1.18. The configuration of the FOSCSEL register starts with IESO bit. To be able to transition from an internal FRC clock to an external primary oscillator, the IESO bit is set. FNOSC = 7 indicates that the starting clock originates from the FRC oscillator and goes through the FRCDIV unit to produce FOSC. In the FOSC register configuration, both clock switching and fail-safe clock monitoring are enabled; therefore, FCKSM = 0. OSCIOFNC = 1 makes OSC2 a primary oscillator pin. POSCMD = 1 activates the XT oscillator as the primary oscillator. The main function programs the initial FRC oscillator frequency, and prepares to engage the XT oscillator with the PLL.
1
Oscillator Configuration
21
#include // This program switches from a low freq. frc oscillator to a XT oscillator with PLL #include // Configure FOSCSEL #pragma config IESO = 1 #pragma config FNOSC = 7
// two-speed oscillator start-up is enabled // initial clock at reset is the FRC oscillator with divide N
// Configure FOSC #pragma config FCKSM = 0 // clock switch enabled, fail-safe clock monitor enabled #pragma config OSCIOFNC = 1 // OSC2 is a clock pin #pragma config POSCMD = 1 // turn on the primary XT oscillator prior to switch-over int main () { ... }
Fig. 1.18 Configuration of clock switching from an FRC to an XT oscillator with PLL
The first instruction in the main function is to tune the initial clock source as shown in Fig. 1.19. OSCTUNbits.TUN = 0 in the OSCTUN register sets the initial clock at 7.37MHz. Since the initial FRC oscillator at 7.37MHz is programmed to go through the FRCDIV unit, and it is supposed to produce the lowest possible initial FRC frequency, the FRCDIV[2:0] entry is set to 256 by CLKDIVbits.FRCDIV = 7. Thus, FOSC becomes equal to 7.37MHz/256 = 28.8KHz. The CLKDIVbits. DOZE = 3 entry divides the peripheral clock frequency, FP, by eight to produce FCY = FOSC/16 = 28.8KHz/16 = 1.8KHz for the CPU. The next clock frequency is assumed to generate from an external primary XT clock. Therefore, the OSCCONbits.NOSC = 3 entry sets the next oscillator to be the XT oscillator in series with the PLL unit. The system clock should not be locked during the switch-over process, which requires OSCCONbits.CLKLOCK = 0. OSCCONbits.LPOSCEN = 0 disables the secondary low power oscillator. Programming the CLKDIV register mostly determines the PLL parameters. Assuming the XT crystal produces 5MHz frequency, CLKDIVbits.PLLPRE = 0 divides this frequency by two, generating 5MHz/2 = 2.5MHz. The PLL multiplies this frequency by 50 due to PLLFBDbits.PLLDIV = 50 and produces 50x2.5MHz = 125MHz at the VCO output. This value is divided by four with CLKDIVbits. PLLPOST = 1 to produce 125MHz/4 = 31.25MHz at the FOSC terminal. If we want the CPU clock frequency to be as high as possible we need to enable the
22
1
Oscillator Configuration
DOZE unit by CLKDIVbits.DOZEN = 1, but keep the DOZE bits at zero by CLKDIVbits.DOZE = 0. This way, FCY becomes equal to FOSC/2 = 31.25MHz/2 = 15.62MHz. As in the previous example, after enabling the switch-over by OSCCONbits. OSWEN = 1, we constantly need to monitor the clock source until the current oscillator actually switches to the XT oscillator by a while-statement, while (OSCCONbits.COSC != 3). After this, we need to wait until the PLL locks by a second while-statement, while (OSCCONbits.LOCK != 1).
// This program switches from a low freq. frc oscillator to a XT oscillator with PLL #include … int main () { // Initial clock source is the FRC oscillator // FRC oscillator is at 7.37MHz OSCTUNbits.TUN = 0; // Initial values of FOSC and FCY at reset CLKDIVbits.FRCDIV = 7; // FRCDIVN = FRC/256 = 7.37/256 = 28.8KHz = FOSC CLKDIVbits.DOZE = 3; // FOSC/16 = 28.8/16 = 1.8KHz = FCY CLKDIVbits.DOZEN = 1; // DOZE ratio is enabled // After switch-over, select the XT oscillator // the system clock will be XT oscillator with PLL OSCCONbits.NOSC = 3; OSCCONbits.CLKLOCK = 0; // the system clock source can be modified OSCCONbits.LPOSCEN = 0; // secondary oscillator is disabled // After switch-over, the clock source is XT oscillator at 5MHz // After switch-over, determine FIN, PLL and FCY frequencies CLKDIVbits.PLLPRE = 0; // VCO input = FIN/2 = 5/2 = 2.5MHz PLLFBDbits.PLLDIV = 50; // VCO output = VCO input*PLLDIV = 2.5*50 = 125MHz CLKDIVbits.PLLPOST = 1; // PLL output = VCO output/4 = 125/4 = 31.25MHz = FOSC CLKDIVbits.DOZE = 0; // FP = 31.25/2 = 15.62MHz, FCY = FP = 15.62MHz CLKDIVbits.DOZEN = 1; // DOZE ratio is enabled
Fig. 1.19 Programming the two-step oscillator switch over to XT oscillator
Reference // Now enable switch-over OSCCONbits.OSWEN = 1;
23
// oscillator switching enabled
// Wait until the oscillator switches to XT with PLL while (OSCCONbits.COSC != 3); // Wait until the PLL locks while (OSCCONbits.LOCK != 1); }
Fig. 1.19 (continued)
Projects 1. Set up the dspic33fj128mc802 microcontroller such that it operates with a single 8MHz internal oscillator clock. Program the configuration and oscillator registers such that the peripheral clock operates at 4MHz and the CPU clock operates at 1MHz. 2. Set up the dspic33fj128mc802 microcontroller to boot from a low frequency internal oscillator and then switch over to a high frequency internal oscillator cascaded with the PLL. The initial 8MHz oscillator produces 4MHz peripheral clock and 2MHz CPU clock during booting. When switch-over occurs, the same 8MHz internal oscillator source is set to produce FP = 40MHz and FCY = 20MHz with the PLL engaged. 3. Set up the dspic33fj128mc802 microcontroller such that it operates with a 10MHz crystal. Even though this is a boundary frequency between XT and HS clocks, choose the XT clock option. Program the configuration and oscillator registers such that the peripheral clock operates at 5MHz and the CPU clock operates at 5MHz. 4. Set up the dspic33fj128mc802 microcontroller such that it uses a low frequency internal oscillator to boot, but then switches over to a high frequency HS oscillator cascaded with the PLL. The initial 8MHz internal oscillator should produce 4MHz peripheral clock and 4MHz CPU clock. However, when the clock switch-over occurs, a 40MHz HS crystal engages. The PLL needs to be set such that the oscillator circuit produces FP = 25MHz and FCY = 6.25MHz. 5. Find means to observe the FOSC, FP and FCY clock frequencies with an oscilloscope. Can OSC1 and OSC2 (or SOSCI and SOSCO) terminals be used to measure the programmed clock frequencies?
Reference 1. dspic33fj128mc802/804 Microchip processor datasheet, https://www.microchip.com/en-us/ product/dsPIC33FJ128MC802, Accessed January 2023.
Chapter 2
Input/Output Ports
2.1
dspic33fj128mc802/804 Architecture
The dspic33fj128mc802 [1] has a 16-bit processor with a 128KB program memory and two 16KB data memories (Data X and Data Y) as shown in Fig. 2.1. Program counter generates the address for the 23-bit wide program memory. The latched instruction in the instruction register is decomposed into opcode and operand fields. If the operand is an address for the register file, the stored data is fetched from the register file and executed in the ALU. The result can go back to the data memory via X Data bus, the ports (port A or port B), any of the peripheral units or back to the ALU for further processing.
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 A. Bindal, Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers, https://doi.org/10.1007/978-3-031-27841-9_2
25
26
2 Y Data Bus
16
X Data Bus
16 H
F Program Counter
Data Latch
Data Latch
Data X Memory
Data Y Memory
M PORT A
DMA RAM
A Address Latch
Input/Output Ports
J PORT B
Program Memory
Address Latch
DMA Controller
Address Latch
H
K
Address Generator
Data Latch
PPS unit
B
control signals to other blocks
G
Instruction Decode and Control
Instruction Register
Power-up Timer FRC and LPRC int. oscillators
XT, HS and EC ext. oscillators
DSP Engine
C
Register File
Oscillator Start-up Timer
L
Divide Support
D
Power-on Reset
ALU
Watchdog Timer
E
Brown-out Reset
Timers
ADC
Output Compare
PWM1
PWM2
SPI N
Comparator
Master Parallel Port
DAC
UART
I2C
Fig. 2.1 Simplified dspic33fj128mc802/804 chip architecture
2.2
dspic33fj128mc802 Input/Output
This processor is equipped with a Digital Signal Processing (DSP) engine with divide support. The Direct Memory access (DMA) unit can be employed to carry out mundane data transfers between two memories, between an I/O port and memory (Data Y memory), or between an I/O port and peripheral. The internal oscillators, such as FRC and LPRC, or external crystal-driven oscillators, such as XT, HS or EC, are used to operate the system and Watch Dog timer. The dspic33fj128mc802 chip has 28 physical pins as shown in Fig. 2.2. Some of the peripheral pins in the datasheet are deliberately omitted from this figure to simplify the overall topology.
2.2
dspic33fj128mc802 Input/Output
27
1
28
AVDD
AN0 / RA0
2
27
AVSS
AN1 / RA1
3
26
PWM1L1 / RP15 / RB15
AN2 / RP0 / RB0
4
25
PWM1H1 / RP14 / RB14
AN3 / RP1 / RB1
5
24
PWM1L2 / RP13 / RB13
AN4 / RP2 / RB2
6
23
PWM1H2 / RP12 / RB12
AN5 / RP3 / RB3
7
22
PWM1L3 / RP11 / RB11
VSS
8
21
PWM1H3 / RP10 / RB10
OSC1 / CLKI / RA2
9
20
VCAP
OSC2 / CLKO / RA3
10
19
VSS
SOSCI / RP4 / RB4
11
18
PWM2L1 / SDA1 / RP9 / RB9
SOSCO / RA4
12
17
PWM2H1 / SCL1 / RP8 / RB8
VDD
13
16
INTR0 / RP7 / RB7
RP5 / RB5
14
15
RP6 / RB6
dsPIC33fj128MC802
MCLR
Fig. 2.2 Simplified dspic33fj128mc802 pin topology
There are five general-purpose port-A pins, RA[4:0], and 16 general-purpose port-B pins, RB[15:0], for digital I/O. Most of the digital port-A I/O pins can be configured to receive analog inputs, AN[5:0]. The entire digital and analog I/O pin list for dspic33fj128mc802 is given in Table 2.1. Table 2.1 General-purpose digital and analog I/O pins Digital General-Purpose I/O Pins
Digital General-Purpose I/O Pins
RA0 – Digital I/O : Pin 2
RB0 – Digital I/O : Pin 4
RA1 – Digital I/O : Pin 3
RB1 – Digital I/O : Pin 5 RB2 – Digital I/O : Pin 6 RB3 – Digital I/O : Pin 7
RA2 – Digital I/O : Pin 9 RA3 – Digital I/O : Pin 10 RA4 – Digital I/O : Pin 12 Analog Input Pins AN0 – Analog input : Pin 2 AN1 – Analog input : Pin 3 AN2 – Analog input : Pin 4 AN3 – Analog input : Pin 5 AN4 – Analog input : Pin 6 AN5 – Analog input : Pin 7
RB4 RB5 RB6 RB7 RB8
– Digital I/O : Pin 11 – Digital I/O : Pin 14 – Digital I/O : Pin 15 – Digital I/O : Pin 16 – Digital I/O : Pin 17
RB9 – Digital I/O : Pin 18 RB10 – Digital I/O : Pin 21 RB11 – Digital I/O : Pin 22 RB12 – Digital I/O : Pin 23 RB13 – Digital I/O : Pin 24 RB14 – Digital I/O : Pin 25 RB15 – Digital I/O : Pin 26
28
2.3
2
Input/Output Ports
dspic33fj128mc802 Peripheral Pin Select (PPS) System
Data transfer in and out of this chip’s peripheral units are accomplished by 16 peripheral I/O pins, namely RP[15:0], as shown in Table 2.2. Table 2.2 Peripheral I/O pins Digital Peripheral I/O Pins RP0 – Digital peripheral I/O : Pin 4 RP1 – Digital peripheral I/O : Pin 5 RP2 RP3 RP4 RP5 RP6 RP7 RP8
– Digital peripheral I/O : Pin 6 – Digital peripheral I/O : Pin 7 – Digital peripheral I/O : Pin 11 – Digital peripheral I/O : Pin 14 – Digital peripheral I/O : Pin 15 – Digital peripheral I/O : Pin 16 – Digital peripheral I/O : Pin 17
RP9 – Digital peripheral I/O : Pin 18 RP10 – Digital peripheral I/O : Pin 21 RP11 – Digital peripheral I/O : Pin 22 RP12 – Digital peripheral I/O : Pin 23 RP13 – Digital peripheral I/O : Pin 24 RP14 – Digital peripheral I/O : Pin 25 RP15 – Digital peripheral I/O : Pin 26
Anytime one of the native peripherals in Fig. 2.1 needs to be accessed, an RP-type pin is used. However, there may be multiple inputs or outputs assigned to a single physical pin in Fig. 2.2. This need calls for Peripheral Pin Select (PPS) mechanism. In the PPS system, an RP-type pin can be connected to peripheral input using a 16-1 MUX in Fig. 2.3. The port selection is accomplished by a five-bit PPS register.
2.3
dspic33fj128mc802 Peripheral Pin Select (PPS) System
Fig. 2.3 PPS input pin selection
29
RP0
0
RP1
1
RP2
2
RP3
3
RP4
4
RP5
5
RP6
6
RP7
7
RP8
8
RP9
9
RP10
10
RP11
11
RP12
12
RP13
13
RP14
14
RP15
15
Peripheral input
5-bit PPS input register
The five-bit PPS input registers are physically located inside the RPINR0 to RPINR26 registers. The complete list is given in Table 2.3.
30
2
Input/Output Ports
Table 2.3 PPS input register list 5-BIT PPS INPUT REGISTER
PERIPHERAL NAME
RPINR0[12:8] RPINR1[4:0] RPINR3[4:0] RPINR3[12:8] RPINR4[4:0] RPINR4[12:8] RPINR7[4:0] RPINR7[12:8] RPINR10[4:0]
External interrupt 1 (INT1) External interrupt 2 (INT2) Timer 2 external clock (T2CK) Timer 3 external clock (T3CK) Timer 4 external clock (T4CK) Timer 5 external clock (T5CK) Input capture 1 (IC1) Input capture 2 (IC2) Input capture 7 (IC7)
RPINR10[12:8] RPINR11[4:0] RPINR12[4:0] RPINR13[4:0] RPINR14[4;0] RPINR14[12:8] RPINR15[4:0] RPINR16[4:0] RPINR16[12:8] RPINR17[4:0]
Input capture 8 (IC8) Output compare fault A (OCFA) PWM1 fault (FLTA1) PWM2 fault (FLTA2) QEI1 phase A (QEA1) QEI1 phase B (QEB1) QEI1 index (INDX1) QEI2 phase A (QEA2)
RPINR18[4:0] RPINR18[12:8] RPINR19[4;0] RPINR19[12:8] RPINR20[4:0] RPINR20[12:8] RPINR21[4:0] RPINR22[4:0] RPINR22[12:8] RPINR23[4:0] RPINR26[4:0]
QEI2 phase B (QEB2) QEI2 index (INDX2) UART1 receive (U1RX) UART1 clear to send (U1CTS) UART2 receive (U2RX) UART2 clear to send (U2CTS) SPI1 data input (SDI1) SPI1 clock input (SCK1) SPI1 slave select input (SS1) SPI2 data input (SDI2) SPI2 clock input (SCK2) SPI2 slave select input (SS2) ECAN1 receive (CIRX)
For example, if RPINR0[12:8] = 0, then this setting turns on port 0 of the 16-1 MUX in Fig. 2.3, and connects RP0 to INT1 (interrupt 1) terminal. Similarly, RPINR1[4:0] = 0 turns on port 0, but connects RP0 to INT2 (interrupt 2) input. When RPINR20[4:0] =15, this time RP15 is connected to SDI1; whereas RPINR20 [12:8] =1 connects RP1 to SCK1. A more detailed input PPS system based on Table 2.3 is given in Fig. 2.4.
2.3
dspic33fj128mc802 Peripheral Pin Select (PPS) System
Fig. 2.4 Complete input PPS system
31
RP0
0
RP1
1
RP2
2
RP15
15
INT1
RPINR0[12:8]
RP0
0
RP1
1
RP2
2
RP15
15
INT2
RPINR1[4:0]
RP0
0
RP1
1
RP2
2
RP15
15
RPINR26[4:0]
CIRX
32
2
Input/Output Ports
To be able to connect a peripheral output to a physical pin, the following 20-1 MUX in Fig. 2.5 is used. Again, a five bit PPS output register selects the appropriate port in this MUX, and connects the desired peripheral output to an RP pin. In this figure, the NULL pin at port 0 is the default input pin connected to the output of a LAT register. When the user wants to write data to a specific RP pin, port 0 must be selected in the embedded program. This way, the parallel bus first delivers the user data to the specified LAT register, and then the data goes through the active port 0 to reach the selected RP pin in the PPS system. Fig. 2.5 PPS output pin selection
NULL
0
Comparator1 output - C1OUT
1
Comparator2 output - C2OUT
2
UART1 transmit output - U1TX
3
UART1 ready to send - U1RTS
4
UART2 transmit output - U2TX
5
UART2 ready to send - U2RTS
6
SPI1 data output - SDO1
7
SPI1 clock output - SCK1
8
SPI1 slave select output - SS1
9
SPI2 data output - SDO2
10
SPI2 clock output - SCK2
11
SPI2 slave select output - SS2
12
ECAN1 transmit output - C1TX
16
Output compare1 output - OC1
18
Output compare2 output - OC2
19
Output compare3 output - OC3
20
Output compare4 output - OC4
21
QEI1 direction status - UPDN1
26
QEI2 direction status - UPDN2
27
5-bit PPS output register
RP pin
2.3
dspic33fj128mc802 Peripheral Pin Select (PPS) System
33
As in PPS input registers, the five-bit PPS output registers are physically located inside the RPOR0 to RPOR7 registers. The complete list is given in Table 2.4. Table 2.4 PPS output register list 5-BIT PPS OUTPUT REGISTER
RP PIN NAME
RPOR0[4:0] RPOR0[12:8] RPOR1[4:0] RPOR1[12:8] RPOR2[4:0] RPOR2[12:8] RPOR3[4:0] RPOR3[12:8] RPOR4[4:0] RPOR4[12:8] RPOR5[4:0] RPOR5[12:8] RPOR6[4:0]
RP0 RP1 RP2 RP3 RP4 RP5 RP6 RP7 RP8 RP9 RP10 RP11 RP12
RPOR6[12:8] RPOR7[4:0] RPOR7[12:8]
RP13 RP14 RP15
For example, if RPOR0[4:0] = 0, then this setting turns on port 0 of the 20-1 MUX in Fig. 2.5, and connects NULL terminal to RP0. Similarly, RPOR0[12:8] = 7 turns on port 7, and connects SDO1 to RP1. When RPOR7[4:0] = 0, this time NULL pin goes to RP14. Similarly, if RPOR7[12:8] = 7, then SDO1 connects to RP15. A more detailed output PPS system based on Fig. 2.5 is given in Fig. 2.6.
34 Fig. 2.6 Complete output PPS system
2 Input/Output Ports
NULL
0
Comparator1 output - C1OUT
1
Comparator2 output - C2OUT
2 RP0
QEI2 direction status - UPDN2
27
RPOR0[4:0]
NULL
0
Comparator1 output - C1OUT
1
Comparator2 output - C2OUT
2 RP1
QEI2 direction status - UPDN2
27
RPOR0[12:8]
NULL
0
Comparator1 output - C1OUT
1
Comparator2 output - C2OUT
2 RP15
QEI2 direction status - UPDN2
27
RPOR7[12:8]
2.4
2.4
dspic33fj128mc802 Physical I/O Pin Descriptions
35
dspic33fj128mc802 Physical I/O Pin Descriptions
Besides the general purpose I/O and PPS pins, there are dedicated Pulse Width Modulation (PWM) pins from the dspic33fj128mc802 as shown in Table 2.5. Basically, there are two separate PWM units, PWM1 and PWM2. PWM1 supports three high-phase and three low-phase PWM signals to operate a brushless motor that needs three phase-shifted signals. PWM2 produces a single high-phase and low-phase PWM signal. All eight PWM outputs can theoretically be used to operate eight independent servo motors if the need arises. Table 2.5 PWM output pins PWM1 and PWM2 Output Pins PWM1L1 (PWM1 low-phase 1 terminal) : Pin 26 PWM1H1 (PWM1 high-phase 1 terminal) : Pin 25 PWM1L2 (PWM1 low-phase 2 terminal) : Pin 24 PWM1H2 (PWM1 high-phase 2 terminal) : Pin 23 PWM1L3 (PWM1 low-phase 3 terminal) : Pin 22 PWM1H3 (PWM1 high-phase 3 terminal) : Pin 21 PWM2L1 (PWM2 low-phase 1 terminal) : Pin 18 PWM2H1 (PWM2 high-phase 1 terminal) : Pin 17
The voltage pins for dspic33fj128mc802 are included in Table 2.6. The digital power supply voltage, VDD, and analog power supply voltage, AVDD, are separated as the digital and analog ground terminals, VSS and AVSS. VCAP is the logic filter capacitor to eliminate sudden spikes on power supply. VREF+ and VREF- are the analog voltage high and low voltage references, respectively. The active-low, master clear bit, MCLR, is used to reset the entire chip. The oscillator terminals for an external crystal are listed in Table 2.7. The primary crystal inputs, OSC1 and OSC2, support XT-type (3MHz to 10MHz), HS-type (10MHz to 40MHz) crystals or an external clock. The secondary oscillator terminals support a crystal oscillating at 32.77KHz for low power applications.
36
2
Input/Output Ports
Table 2.6 Voltage pins Voltage Pins VDD (3.3V DC power source) VSS (Digital ground)
: Pin 13 : Pins 8, 19
AVDD (Positive supply for analog modules) : Pin 28 AVSS (Analog ground) : Pin 27 : Pin 20 VCAP (CPU logic filter capacitor) VREF+ (Analog voltage reference high) : Pin 2 VREF- (Analog voltage reference low) : Pin 3 MCLR (Master clear bit) : Pin 1
Table 2.7 Primary and secondary oscillator I/O pins Primary and Secondary Oscillator I/O Pins OSC1 (Primary crystal input)
: Pin 9
OSC2 (Primary crystal output) SOSCI (Secondary crystal input)
: Pin 10 : Pin 11
SOSCO (Secondary crystal output) CLKI (Primary crystal input – OSC1)
: Pin 12 : Pin 9
CLKO (Primary crystal output – OSC2) : Pin 10
The program data input and debugging are managed through one of the three channels, PGED1/PGEC1, PGED2/PGEC2 or PGED3/PGEC3, depending on the application as shown in Table 2.8. Any PICKIT3-type programmer can be employed using these terminals to load the application program to dspic33fj128mc802/804. Table 2.8 Program and debug output pins Program/Debug Pins PGED1 (Data I/O for prog/debug comm channel 1)
: Pin 4
PGEC1 (Clock input for prog/debug comm channel 1) : Pin 5 PGED2 (Data I/O for prog/debug comm channel 2) : Pin 21 PGEC2 (Clock input for prog/debug comm channel 2) : Pin 22 PGED3 (Data I/O for prog/debug comm channel 3) : Pin 14 PGEC3 (Clock input for prog/debug comm channel 3) : Pin 15
The Parallel Master Port (PMP) module is a parallel 8-bit I/O module designed to communicate with a wide variety of devices that contain parallel port as shown in Table 2.9. These include communication peripherals, LCDs, external memory
2.4
dspic33fj128mc802 Physical I/O Pin Descriptions
37
devices and microcontrollers. This port becomes especially important when the dspic33fj128mc802 master needs to communicate with eight-bit slave processors. Similarly, when the dspic33fj128mc802 becomes a slave device for a 32-bit pic32mz-type processor, communication between these two processors is maintained using the master parallel port. Table 2.9 Parallel master address and data pins Parallel Master I/O Pins PMA0 (Parallel master address bit 0 input) : Pin 10 PMA1 (Parallel master address bit 1 input) : Pin 12 PMBE (Parallel master byte enable strobe) : Pin 11 PMCS1 (Parallel master chip select 1 strobe) : Pin 26 PMWR (Parallel master write strobe) : Pin 25 PMRD (Parallel master read strobe) : Pin 24 PMD0 (Parallel master data bit 0) : Pin 23 PMD1 (Parallel master data bit 1) : Pin 22 PMD2 (Parallel master data bit 2) : Pin 21 PMD3 (Parallel master data bit 3) : Pin 18 PMD4 (Parallel master data bit 4) : Pin 17 PMD5 (Parallel master data bit 5) : Pin 16 PMD6 (Parallel master data bit 6) : Pin 15 PMD7 (Parallel master data bit 7) : Pin 14
Other essential peripheral pins are listed in Table 2.10. In this list, there are four analog comparator input pins: one for external clock input for Timer 1, two pins for clock and data lines of I2C serial bus, one for interrupt 0 (INT0), and four JTAG pins for external test devices. Table 2.10 Miscellaneous other I/O pins Miscellenous Pins C1IN+ (Analog comparator 1 positive input) : Pin 7 C1IN- (Analog comparator 1 negative input) : Pin 6 C2IN+ (Analog comparator 2 positive input) : Pin 5 C2IN- (Analog comparator 2 negative input) : Pin 4 T1CK (Timer 1 external clock input) : Pin 12 SDA1 (I2C data I/O port) : Pin 18 SCL1 (I2C clock I/O port) : Pin 17 INT0 (external interrupt input) : Pin 16 TMS (JTAG test select pin) : Pin 22 TDI (JTAG test data input pin) : Pin 21 TDO (JTAG test data output pin) : Pin 18 TCK (JTAG test clock input pin) : Pin 17
38
2
2.5
Input/Output Ports
TRIS, PORT and LAT Registers in dspic33fjmc802
Three different types of registers are used to read or write data to a general-purpose digital I/O pin: TRIS registers, PORT registers and LAT registers. The TRIS register defines the direction of data. When a data bit needs to be inputted to the processor or a peripheral, the corresponding bit in the TRIS register becomes to logic 1. To output data, the corresponding TRIS register bit is set to logic 0. There are equal number of TRIS bits as there are physical RA[4:0] and RB[15:0] pins. A complete description of TRIS bit assignments are given in Fig. 2.7a, b. a 15 14 13 12 11 10 -
-
-
-
-
-
9
8
7
6
5
-
-
-
-
-
4
3
2
1
0
TRISA[4:0] TRISA[4:0] entry in TRISA register 11111 = RA4 input, RA3 input, RA2 input, RA1 input, RA0 input 11110 = RA4 input, RA3 input, RA2 input, RA1 input, RA0 output 11101 = RA4 input, RA3 input, RA2 input, RA1 output, RA0 input ... 00010 = RA4 output, RA3 output, RA2 output, RA1 input, RA0 output 00001 = RA4 output, RA3 output, RA2 output, RA1 output, RA0 input 00000 = RA4 output, RA3 output, RA2 output, RA1 output, RA0 output
b 15 14 13 12 11 10
9
8
7
6
5
4
3
2
1
0
TRISB[15:0] TRISB[15:0] entry in TRISB register 1111 1111 1111 1111 = RB15 input, RB14 input, RB13 input … RB1 input, RB0 input 1111 1111 1111 1110 = RB15 input, RB14 input, RB13 input … RB1 input, RB0 output 1111 1111 1111 1101 = RB15 input, RB14 input, RB13 input … RB1 output, RB0 input ... 0000 0000 0000 0010 = RB15 output, RB14 output, RB13 output … RB1 input, RB0 output 0000 0000 0000 0001 = RB15 output, RB14 output, RB13 output … RB1 output, RB0 input 0000 0000 0000 0000 = RB15 output, RB14 output, RB13 output … RB1 output, RB0 output
Fig. 2.7 (a) TRISA register and directionality table (b) TRISB register and directionality table
2.5
TRIS, PORT and LAT Registers in dspic33fjmc802
39
The PORT register stores data bits to be inputted to the processor or a peripheral. Again, the number of PORT bits is equal to the sum of RA and RB pins shown in Fig. 2.8a, b. For example, PORTB[15:0] = 0xFFFF means the value, 0xFFFF, will be the input for RB[15:0]. Fig. 2.8 (a) PORTA register and data input table (b) PORTB register and data input table
a 15 14 13 12 11 10 -
-
-
-
-
-
9
8
7
6
5
-
-
-
-
-
4
3
2
1
0
PORTA[4:0] PORTA[4:0] entry in PORTA register 11111 = 1 → RA4, 1 → RA3, 1 → RA2, 1 → RA1, 1 → RA0 11110 = 1 → RA4, 1 → RA3, 1 → RA2, 1 → RA1, 0 → RA0 11101 = 1 → RA4, 1 → RA3, 1 → RA2, 0 → RA1, 1 → RA0 ... 00010 = 0 → RA4, 0 → RA3, 0 → RA2, 1 → RA1, 0 → RA0 00001 = 0 → RA4, 0 → RA3, 0 → RA2, 0 → RA1, 1 → RA0 00000 = 0 → RA4, 0 → RA3, 0 → RA2, 0 → RA1, 0 → RA0
b 15 14 13 12 11 10
9
8
7
6
5
4
3
2
1
0
PORTB[15:0] PORTB[15:0] entry in PORTB register 1111 1111 1111 1111 = 1 → RB15, 1 → RB14 … 1 → RB1, 1 → RB0 1111 1111 1111 1110 = 1 → RB15, 1 → RB14 … 1 → RB1, 0 → RB0 1111 1111 1111 1101 = 1 → RB15, 1 → RB14 … 0 → RB1, 1 → RB0 ... 0000 0000 0000 0010 = 0 → RB15, 0 → RB14 … 1 → RB1, 0 → RB0 0000 0000 0000 0001 = 0 → RB15, 0 → RB14 … 0 → RB1, 1 → RB0 0000 0000 0000 0000 = 0 → RB15, 0 → RB14 … 0 → RB1, 0 → RB0
40
Input/Output Ports
2
Finally, the LAT register stores data bits to be outputted from the processor or a peripheral to a physical I/O pin. Fig. 2.9a, b show LATA and LATB registers and data output table for each register. For example, LATA[4:0] = 11110 means logic 0 will be outputted from RA0, but the rest of the RA pins, from RA1 to RA4 will produce logic 1. a 15 14 13 12 11 10 -
-
-
-
-
-
9
8
7
6
5
-
-
-
-
-
4
3
2
1
0
LATA[4:0] LATA[4:0] entry in LATA register 11111 = RA4 → 1, RA3 → 1, RA2 → 1, RA1 → 1, RA0 → 1 11110 = RA4 → 1, RA3 → 1, RA2 → 1, RA1 → 1, RA0 → 0 11101 = RA4 → 1, RA3 → 1, RA2 → 1, RA1 → 0, RA0 → 1 ... 00010 = RA4 → 0, RA3 → 0, RA2 → 0, RA1 → 1, RA0 → 0 00001 = RA4 → 0, RA3 → 0, RA2 → 0, RA1 → 0, RA0 → 1 00000 = RA4 → 0, RA3 → 0, RA2 → 0, RA1 → 0, RA0 → 0
b 15 14 13 12 11 10
9
8
7
6
5
4
3
2
1
0
LATB[15:0] LATB[15:0] entry in LATB register 1111 1111 1111 1111 = RB15 → 1, RB14 → 1 … RB1 → 1, RB0 → 1 1111 1111 1111 1110 = RB15 → 1, RB14 → 1 … RB1 → 1, RB0 → 0 1111 1111 1111 1101 = RB15 → 1, RB14 → 1 … RB1 → 0, RB0 → 1 ... 0000 0000 0000 0010 = RB15 → 0, RB14 → 0 … RB1 → 1, RB0 → 0 0000 0000 0000 0001 = RB15 → 0, RB14 → 0 … RB1 → 0, RB0 → 1 0000 0000 0000 0000 = RB15 → 0, RB14 → 0 … RB1 → 0, RB0 → 0
Fig. 2.9 (a) LATA register and data output table (b) LATB register and data output table
2.5
TRIS, PORT and LAT Registers in dspic33fjmc802
41
Fig. 2.10 shows a more practical view of an I/O port, integrating TRIS, PORT, LAT and PCFG (Analog and Digital Pin Configuration – AD1PCFG) registers as one unit. Write to LAT Reg
Write to I/O pin
TRIS = 0
LAT REG
DATA BUS
I/O PIN
TRIS = 1
PORT REG Read from PORT Reg
Read from I/O pin
TO ADC
PCFG REG
TRIS REG Write to TRIS Reg
Fig. 2.10 Integrated I/O port with TRIS, PORT, LAT and PCFG registers
According to this figure, if we need to output bit 0 of the XYZ register in dspic33fj128mc802 to the physical pin, RB4, we need to configure the TRIS and LAT register bits as follows: TRISBbits.TRISB4 = 0; // RB4 is defined as a digital output pin LATBbits.LATB4 = XYZbits.bit0; // Bit 0 of the XYZ register is written to // the LATB register and then to RB4
Here, TRISBbits defines the entire TRISB register. The extension to TRISBbits, TRISB4, defines the bit that needs to be set to logic 0 to configure RB4 as an output as shown in Fig. 2.10. Similarly, LATBbits refers to the entire LATB register. The extension to LATBbits, LATB4, is the bit stored in the XYZ register before it is outputted to RB4. Similarly, if the digital value on RB5 needs to be inputted to bit 7 of the XYZ register, then the following program applies:
42
2
TRISBbits.TRISB5 = 1; XYZbits.bit7 = PORTBbits.RB5;
Input/Output Ports
// RB5 is defined as a digital input pin // RB5 is written to bit 5 of the PORTB register // then to bit 7 of the XYZ register
The same naming convention used earlier in the TRISB and LATB registers applies to this example as well. Here, bit 5 of the TRISB register is set to logic 1, which enables the tri-state buffer in Fig. 2.10, and routes the external I/O pin, RB5, to bit 5 of the PORTB register. Once stored, bit 5 of the PORTB register is written to bit 7 of the XYZ register.
2.6
Parallel Port Examples with dspic33fj128mc802
Example 2.1: To show a complete picture how an I/O device interacts with dspic33fj128mc802, the following example in Fig. 2.11 is given [2]. In this example, as soon as the pushbutton switch is depressed, the RA0 pin senses VDD as logic 1, and turns on the LED connected to the RA1 pin. When the pushbutton is released, the RA0 pin does not sense any voltage but 0V, and turns off the LED. Fig. 2.11 Pushbutton switch example
VDD
dspic33fj128mc802 microcontroller
pushbutton switch RA0 - input
R
RA1 - output
R
LED
2.6
Parallel Port Examples with dspic33fj128mc802
43
The embedded program to operate the pushbutton switch in Fig. 2.11 is given in Program 2.1. We will examine this program segment by segment even though comments are provided for each instruction. Program 2.1
// When VDD is connected to RA0 with a push button switch, the LED at RA1 turns on #include // Configuration Register - FOSCSEL #pragma config IESO = 0 #pragma config FNOSC = 7
// start with a user-selected oscillator at reset // select FRC oscillator with Divide-N at reset
// Configuration Register - FOSC #pragma config FCKSM = 3
// both clock switching and fail-safe modes are disabled
#pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled int main () { // Clock source definition - A single FRC internal clock OSCTUNbits.TUN = 0; CLKDIVbits.FRCDIV = 6;
// select FRC = 7.37MHz // FRCDIVN = FRC/64 = 7.37/64 = 115.15KHz = FOSC
CLKDIVbits.DOZE = 1; CLKDIVbits.DOZEN = 1;
// FOSC/4 = 115.15/4 = 28.8KHz = FCY // DOZE defines the ratio between cpu and peripheral clocks
//unsigned int breakpoint; // I/O definition AD1PCFGL = 0xFFFF;
// Pin RA0 and RA1 are assigned to be analog pins
TRISAbits.TRISA0 = 1; TRISAbits.TRISA1 = 0;
// Pin RA0 is assigned as input // Pin RA1 is assigned as output
while (1) { if (PORTAbits.RA0 == 1) LATAbits.LATA1 = 1; else LATAbits.LATA1 = 0; //breakpoint = 1; } }
44
2
Input/Output Ports
The first part of this program configures the oscillator as shown in Fig. 2.12. For this particular case, we only need an oscillator to operate the CPU because the events taking place in the program is tracked by the CPU. The other parts of the chip are kept non-functional. The pragma statements at the beginning of the program configure the type of the oscillator and its data-path. They program the FOSCSEL and FOSC registers during the system start-up. Note that none of the pragma statements end with a semi colon. The oscillator registers are programmed in the main program, and basically define the oscillator frequency. The first pragma statement, #pragma config IESO = 0, disables any internal-toexternal oscillator switch-over mechanism. It is a common practice in Microchip processors to start with an internal oscillator and then switch it over to an external (or internal with PLL) oscillator that can produce a stable oscillation at a much higher frequency. In this case, we intend to use an internal FRC-type oscillator that produces 7.37MHz frequency. The second pragma statement, #pragma config FNOSC = 7, defines which port of the 8-1 MUX in Fig. 1.3 should be turned on when the system starts from reset. In this case, we select port 7 (or S7) because we want to employ the internal FRC oscillator with FRCDIVN output. We also opt not to engage clock switching or fail-safe modes. Therefore, the pragma statement, #pragma config FCKSM = 3, turns off both of these features. We also disable the OSCO terminal from being a crystal pin. Therefore, #pragma config OSCIOFNC = 0 statement makes the OSCO terminal a general-purpose I/O pin. Since we do not intend to use any of the HS, XT or EC-type primary oscillators, we use #pragma config POSCMD = 3. The statement, #pragma config IOL1WAY = 1, allows a single reconfiguration during clock switch-over, but it is not essential here to operate the internal FRC oscillator. To program the oscillation frequency for the CPU and peripheral clocks we use OSCTUN and CLKDIV registers. The OSCTUN register defines the FRC clock frequency. Therefore, OSCTUNbits.TUN = 0 statement tunes the FRC clock at 7.37MHz. The CLKDIV register divides the clock frequency and defines the CPU and peripheral clock frequencies. The statement, CLKDIVbits.FRCDIV = 6, divides the center 7.37MHz FRC clock frequency by 64 and produces 115.15KHz at the FRCDIVN port in Fig. 1.3. Since port 7 of the 8-1 MUX is already turned on, FRCDIVN = 115.15KHz becomes the frequency at the FOSC terminal in Fig. 1.3. The statement, CLKDIVbits.DOZE = 1, further divides FOSC by four and defines the CPU frequency, FCY = 115.15KHz / 4 = 28.8KHz. Finally, CLKDIVbits. DOZEN = 1 enables the DOZE unit that produces the clock for the CPU.
2.6
Parallel Port Examples with dspic33fj128mc802
45
// In this example, when VDD is connected to RA0 with a push button switch, the LED at RA1 pin turns on #include // Configuration Register - FOSCSEL #pragma config IESO=0 // start with a user-selected oscillator at reset #pragma config FNOSC=7 // select FRC oscillator with Divide-N at reset // Configuration Register - FOSC #pragma config FCKSM=3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC=0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD=3 // primary oscillators are disabled int main () { // Clock source definition - A single FRC internal clock OSCTUNbits.TUN=0; // select FRC = 7.37MHz CLKDIVbits.FRCDIV=6; // FRCDIVN = FRC/64 = 7.37/64 = 115.15KHz -> FOSC CLKDIVbits.DOZE=1; // FCY = FOSC/4 = 115.15/4 = 28.8KHz CLKDIVbits.DOZEN=1; // DOZE defines the ratio between cpu and peripheral clocks ... }
Fig. 2.12 Configuration of the oscillator for the Pushbutton Switch program
Once we define the clock frequency for the CPU, the next step is to define the I/O pins for the pushbutton switch and the LED as shown in Fig. 2.13. Fig. 2.11 configures the RA0 as an input pin and RA1 as an output pin. Therefore the statement, TRISAbits.TRISA0 = 1 makes RA0 an input pin due to Fig. 2.10. Similarly, TRISAbits.TRISA1 = 0 configures RA1 to be an output pin. To define RA0 and RA1 as general-purpose digital pins, we need to program the configuration register of the Analog-to-Digital Converter (ADC) unit because RA0 and RA1 share the same physical pins with AN0 and AN1 according to Fig. 2.2. Thus, the statement, AD1PCFGL = 0xFFFF, makes all AN-type pins, AN0 to AN5, to be general-purpose digital I/O pins. The ADC operation and ADC registers will be explained in great detail in Chapter 5 along with the true nature of analog pins, AN0 to AN5.
46
2
Input/Output Ports
// In this example, when VDD is connected to RA0 with a push button switch, the LED at RA1 pin turns on #include ... int main () { // Clock source definition - A single FRC internal clock ... // I/O definition TRISAbits.TRISA0 = 1; TRISAbits.TRISA1 = 0; AD1PCFGL = 0xFFFF;
// Pin RA0 is assigned as input // Pin RA1 is assigned as output // Pin RA0 and RA1 are assigned to be digital pins
... }
Fig. 2.13 Configuration of the I/O pins for the Pushbutton Switch
The pushbutton-LED operation is explained in Fig. 2.14. The statement, while (1), converts this program into a continuous loop as long as pushbutton is depressed. The first statement in the while-loop, if (PORTAbits.RA0 == 1), looks for an instance when the pushbutton switch is depressed. If it is depressed, the program executes the statement, LATAbits.LATA1 = 1, which applies a logic 1 to the output pin RA1 that turns on the LED. If the switch is not depressed, logic 0 is applied to RA1, and the LED turns off.
2.6
Parallel Port Examples with dspic33fj128mc802
47
// In this example, when VDD is connected to RA0 with a push button switch, the LED at RA1 pin turns on #include ... int main () { … while (1) { if (PORTAbits.RA0 == 1) LATAbits.LATA1 = 1; else LATAbits.LATA1 = 0; } }
Fig. 2.14 Operation of the Pushbutton Switch
Example 2.2: The second example [2] illustrates a blinking LED. To be consistent with the first example, this example also treats RA0 as an input pin and RA1 as an output pin. The entire program is shown in Program 2.2.
48
2
Input/Output Ports
Program 2.2 // When a push button switch is depressed the LED blinks #include // Configuration Register - FOSCSEL #pragma config IESO = 0 #pragma config FNOSC = 7
// start with a user-selected oscillator at reset // select FRC oscillator with Divide-N at reset
// Configuration Register - FOSC #pragma config FCKSM = 3
// both clock switching and fail-safe modes are disabled
#pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled unsigned int limit1, limit2; void delay(limit1, limit2) { unsigned int i, j; for (i = 0; i < limit1; i++) { for (j = 0; j < limit2; j++); } } int main () { // Clock source definition - A single FRC internal clock OSCTUNbits.TUN = 0; // select FRC = 7.37MHz CLKDIVbits.FRCDIV = 6; // FRCDIVN = FRC/64 = 7.37/64 = 115.15KHz = FOSC CLKDIVbits.DOZE = 1; // FOSC/4 = 115.15/4 = 28.8KHz = FP CLKDIVbits.DOZEN = 1; // DOZE defines the ratio between cpu and peripheral clocks // I/O definition AD1PCFGL = 0xFFFF; TRISAbits.TRISA0 = 1;
// Pin RA0 and RA1 are assigned to be analog pins // Pin RA0 is assigned as input
TRISAbits.TRISA1 = 0;
// Pin RA1 is assigned as output
while (1) { if (PORTAbits.RA0 == 1) { LATAbits.LATA1 = 0; delay(100, 100); LATAbits.LATA1 = 1; delay(10, 10); }
2.6
Parallel Port Examples with dspic33fj128mc802
49
else LATAbits.LATA1 = 0; } }
As in the previous example, this program is also divided into three different segments. The first segment is the clock generation for the CPU as shown in Fig. 2.15, which is identical to the clock generation program for the pushbutton switch in Fig. 2.12, and produces 28.8KHz clock for the CPU.
// When a push button switch is depressed LED blinks #include // Configuration Register - FOSCSEL #pragma config IESO=0 // start with a user-selected oscillator at reset #pragma config FNOSC=7 // select FRC oscillator with Divide-N at reset // Configuration Register - FOSC #pragma config FCKSM=3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC=0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD=3 // primary oscillators are disabled … int main () { // Clock source definition - A single FRC internal clock OSCTUNbits.TUN=0; // select FRC = 7.37MHz CLKDIVbits.FRCDIV=6; // FRCDIVN = FRC/64 = 7.37/64 = 115.15KHz -> FOSC CLKDIVbits.DOZE=1; // FCY = FOSC/4 = 115.15/4 = 28.8KHz CLKDIVbits.DOZEN=1; // DOZE defines the ratio between cpu and peripheral clocks ... }
Fig. 2.15 Configuration of the oscillator for the Blinking LED program
50
2
Input/Output Ports
The second segment determines the directionality and the nature of the I/O ports as shown in Fig. 2.16. Again, the statement, TRISAbits.TRISA0 = 1, makes RA0 an input, and TRISAbits.TRISA1 = 1, makes RA1 an output as in the previous example. The ADC configuration statement, AD1PCFGL = 0xFFFF, makes RA0 and RA1 to be digital I/O pins since they share the same physical pins with AN0 and AN1. // When a push button switch is depressed LED blinks #include … int main () ... // I/O definition TRISAbits.TRISA0 = 1; // Pin RA0 is assigned as input TRISAbits.TRISA1 = 0; // Pin RA1 is assigned as output AD1PCFGL = 0xFFFF; // Pin RA0 and RA1 are assigned to be digital pins … }
Fig. 2.16 Configuration of the I/O pins for the Blinking LED program
The operation of Blinking LED shown in Fig. 2.17 is the third segment of Program 2.2. The function call, void delay(limit1, limit2), creates a long delay by two cascading variables limit1 and limit2. When the pushbutton switch is depressed through the statement, if (PORTAbits.RA0 == 1), the LED initially turns off because the RA1 pin does not produce any voltage due to the LATAbits.LATA1 = 0 statement. The program stays in this state for a period equal to the delay function, delay(100, 100). The statement, LATAbits.LATA1 = 1, produces logic 1 at the RA1 output, and turns on the LED. The program stays in this state for a period equal to a second delay function, delay(10, 10). Since the LED is in a continuous while-loop because of the while (1) statement, LED keeps blinking as long as the pushbutton switch is depressed. When switch button is released, the embedded program removes logic 1 from RA1 output, and the LED stops blinking.
2.6
Parallel Port Examples with dspic33fj128mc802
// When a push button switch is depressed LED blinks #include … unsigned int limit1, limit2; void delay(limit1, limit2) { unsigned int i, j; for (i = 0; i < limit1; i++) { for (j = 0; j < limit2; j++); } } int main () { … while (1) { if (PORTAbits.RA0 == 1) { LATAbits.LATA1 = 0; delay(100, 100); LATAbits.LATA1 = 1; delay(10, 10); } else LATAbits.LATA1 = 0; } }
Fig. 2.17 Operation of the Blinking LED program
51
52
2
2.7
Input/Output Ports
dspic33fj128mc804 Input/Output
22
RA10
RA7
DAC1LP/PWM1H1/RB14/RP14
GND
DAC1LN/PWM1L1/RB15/RP15
AVDD 0.1µF
MCLR
VREF+/AN0/RA0
VREF-/AN1/RA1
DAC1RP/PWM1H2/RB12/RP12
26
8
27
7 6
dspic33fj128mc804
29
5
30
4
31
3
32
2
33
1
35
36
37
38
39
40
41
42
43
PGEC2/PWM1L3/RB11/RP11 PGED2/PWM1H3/RB10/RP10 VCAP (VDD) 0.1µF GND RC9/RP25 RC8/RP24 PWM2L1/RC7/RP23 PWM2H1/RC6/RP22 RB9/RP9
44
RB8/RP8
RA4
34
RB7/RP7
RB4/RP4
12
9
PGEC3/RB6/RP6
RA8
13
25
PGED3/RB5/RP5
OSCO/RA3
14
DAC1RN/PWM1L2/RB13/RP13
RC4/RP20
OSCI/RA2
15
10
RA9
0.1µF
16
11
RC3/RP19
VDD
17
24
28
GND
18
0.1µF
AN8/RB18/RP18
19
VDD
AN6/RB16/RP16 AN7/RB17/RP17
20
GND
AN5/RB3/RP3
21
23
RC5/RP21
AN4/RB2/RP2
PGED1/AN2/RB0/RP0
PGEC1/AN3/RB1/RP1
The dspic33fj128mc804 is the larger sibling of dspic33fj128mc802 and it contains a Digital-to-Analog Converter (DAC). The simplified I/O pin topology and the electrical pin connections are shown in Fig. 2.18.
Fig. 2.18 Simplified dspic33fj128mc804 pin topology
The general purpose digital and analog pins in dspic33fj128mc804 are shown in Table 2.11. This chip offers three additional analog pins and 15 additional digital pins in contrast with dspic33fj128mc802.
2.7
dspic33fj128mc804 Input/Output
53
Table 2.11 General-purpose digital and analog I/O pins in dspic33fj128mc804 Digital General-Purpose I/O Pins
Digital General-Purpose I/O Pins
RA0 – Digital I/O : Pin 19
RB0 – Digital I/O : Pin 21
RA1 – Digital I/O : Pin 20 RA2 – Digital I/O : Pin 30 RA3 – Digital I/O : Pin 31 RA4 – Digital I/O : Pin 34 RA5 – Digital I/O : Pin RA6 – Digital I/O : Pin RA7 – Digital I/O : Pin 13 RA8 – Digital I/O : Pin 32 RA9 – Digital I/O : Pin 35 RA10 – Digital I/O : Pin 12 Digital General-Purpose I/O Pins RC0 – Digital I/O : Pin 25 RC1 – Digital I/O : Pin 26 RC2 – Digital I/O : Pin 27 RC3 – Digital I/O : Pin 36 RC4 – Digital I/O : Pin 37 RC5 – Digital I/O : Pin 38 RC6 – Digital I/O : Pin 2 RC7 – Digital I/O : Pin 3 RC8 – Digital I/O : Pin 4 RC9 – Digital I/O : Pin 5
RB1 – Digital I/O : Pin 22 RB2 RB3 RB4 RB5 RB6 RB7 RB8
– Digital I/O : Pin 23 – Digital I/O : Pin 24 – Digital I/O : Pin 33 – Digital I/O : Pin 41 – Digital I/O : Pin 42 – Digital I/O : Pin 43 – Digital I/O : Pin 44
RB9 – Digital I/O : Pin 1 RB10 – Digital I/O : Pin 8 RB11 – Digital I/O : Pin 9 RB12 – Digital I/O : Pin 10 RB13 – Digital I/O : Pin 11 RB14 – Digital I/O : Pin 14 RB15 – Digital I/O : Pin 15
Analog Input Pins AN0 – Analog input : Pin 19 AN1 – Analog input : Pin 20 AN2 – Analog input : Pin 21 AN3 – Analog input : Pin 22 AN4 – Analog input : Pin 23 AN5 – Analog input : Pin 24 AN6 – Analog input : Pin 25 AN7 – Analog input : Pin 26 AN8 – Analog input : Pin 27
Fig. 2.19 shows the map connecting the device to the Plug-In Module (PIM). The “-“ sign in this figure indicates no device pin connection with the PIM. A device pin may also be connected to more than one physical PIM pin. Finally, the physical PIM topology is shown in Fig. 2.20. Each pin number in this figure associates with the physical device pin in Fig. 2.19. In fact, during the implementation phase of the Digital-to-Analog Converter in Chapter 5 the PIM platform shown in Fig. 2.20 is used with PGEC1 and PGED1 ports connected to PICKIT3 programmer to operate the device.
54
2
Device Pin No
PIM Pin No
Device Pin No
1
19
2
77
3
Input/Output Ports
PIM Pin No
Device Pin No
PIM Pin No
Device Pin No
PIM Pin No
12
-
23
42
34
81
13
71
24
43
35
82
78
14
94
25
-
36
44
4
68, 20
15
93
26
20
37
72
5
69, 21
16
31
27
32
38
76
6
65
17
30
28
16
39
36
7
85
18
13
29
15
40
37
8
3
19
25
30
63
41
27
9
100
20
24
31
64
42
26
10
99
21
35
32
83
43
22, 70
11
98
22
41
33
84
44
18
86 85 84 83 82 81 80 79 78 77 76
89 88 87
90
92 91
100 99 98 97 96 95 94 93
Fig. 2.19 dspic33fj128mc804 device to PIM configuration
1 2 3 4 5 6 7 8 9 10
75 74 73 72 71 70 69 68 67 66
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
Fig. 2.20 dspic33fj128mc804 PIM
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
34 35
26 27 28 29 30 31 32 33
dspic33fj128mc804 100-pin Plug-In Header
65 64 63 62 61 60 59 58 57 56 55 54 53 52 51
References
55
Projects 1. Set up the dspic33fj128mc802 microcontroller with a push-button switch and LED and implement Program 2.1. When the push-button switch is depressed, the LED should turn on constantly. Otherwise, it turns off. 2. Set up the dspic33fj128mc802 microcontroller with an Interlink FS400-type force sensor and LED, and implement Program 2.2. When the force sensor is exposed to pressure, the LED should blink constantly. Otherwise, it turns off. 3. Set up the dspic33fj128mc802 microcontroller with an Interlink FS400-type force sensor and LED according to Program 2.2. As soon as the force sensor is exposed to pressure, the LED should blink constantly. Removing the pressure from the sensor should not stop blinking. To turn off the LED, a second force sensor should be exposed to pressure. 4. Now, imagine four force sensors and four LEDs with different colors. Think of each LED as part of the leg actuation mechanism of a four-legged animal. When the LED is on, that means the foot is retracted from the ground. Now, assume that sensor no. 1 and LED no. 1 correspond to the right front foot, sensor no. 2 and LED no. 2 left front foot, sensor no. 3 and LED no. 3 right rear foot, and sensor no. 4 and LED no. 4 left rear foot. In order to walk, the animal should at least lift one foot from the ground. Write an application program with force sensors and LEDs to simulate walking. 5. Set up the dspic33fj128mc804 PIM with PICKIT 3 programmer to execute the blinking LED in Program 2.2 with push-button switch.
References 1. dspic33fj128mc802/804 Microchip processor datasheet, https://www.microchip.com/en-us/ product/dsPIC33FJ128MC802, Accessed January 2023. 2. MPLABX software design environment, https://www.microchip.com/en-us/tools-resources/ develop/mplab-x-ide, Accessed January 2023.
Chapter 3
Serial Port
Peripheral devices and external buffer memories communicate with the processor using a serial bus. There are currently two serial buses used in low-speed communication. The Serial Peripheral Interface (SPI) was introduced in 1979 by Motorola as an external microprocessor bus for the well-known Motorola 68000 microprocessor. The SPI bus normally requires four wires; however, wire count increments by one every time a peripheral device is added to the system. The second bus, Inter-Integrated Circuit (I2C), was developed by Philips in 1982 to connect Philips CPUs to peripheral chips in a TV set. This bus requires only two wires, but it is considerably slower compared to the SPI bus.
3.1
Introduction to Serial Peripheral Interface (SPI)
SPI is designed as a very straightforward serial bus. Four signals establish all the serial communication between a CPU and a peripheral device. The SPI clock signal, SCK, is distributed to all slaves in a system, and forces each peripheral to be synchronous with a single master. The Slave Select signal (SS) is an active-low signal, and is used to enable a particular slave prior to data transfer. Serial-Data-In (SDI) or Master-Out-Slave-In (MOSI) port is what the master uses to send serial data to a slave. Serial-Data-Out (SDO) or Master-In-Slave-Out (MISO) port is what the master uses to read serial data from a slave. Figure 3.1 shows the serial bus configuration between the bus master and a single slave. All SPI signals with the exception of SDO must be initiated by the bus master.
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 A. Bindal, Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers, https://doi.org/10.1007/978-3-031-27841-9_3
57
58
3
SPI Master
SCK SDO SDI SS
Serial Port
SCK SDI SPI Slave
SDO SS
Fig. 3.1 SPI bus between a master and a single slave
When the bus master is connected to a multitude of slaves, it needs to generate an active-low Slave Select signal for each slave as shown in Fig. 3.2.
SPI Master
SCK SDO
SCK SDI
SPI Slave3
SDI SS1
SDO
SS
SCK SDI
SPI Slave2
SDO
SS
SCK SDI
SPI Slave1
SDO
SS
SS2 SS3
Fig. 3.2 SPI bus between a master and three slaves
SPI is considered a single-master serial communication protocol. This means that only one master is assigned to initiate and carry out all serial communications with slaves. When the SPI master wishes to send or request data from a slave, first it selects a particular slave by lowering the corresponding SS signal to logic 0, and then producing a clock signal for the slave as shown in Fig. 3.3. Once the select and clock signals are established, the master sends out serial data to the selected slave at the negative edge of each SCK from its SDO port while simultaneously samples slave data at the positive edge of each SCK at the SDI port. According to the SPI protocol, the slave is capable of sending and receiving data except it cannot generate SCK. In the following example in Fig. 3.3, the master sends out serial data packets composed of DataM1 (most significant bit) to DataM4 (least significant bit) from its SDO port at the negative edge of SCK, and samples slave data composed of DataS1 (most significant bit) to DataS4 (least significant bit) at its SDI port at the positive edge of SCK. The slave, on the other hand can also dispatch serial data packets
3.1
Introduction to Serial Peripheral Interface (SPI)
59
composed of DataS1 (most significant bit) to DataS4 (least significant bit) from its SDO port at the negative edge of SCK, or sample master data composed of DataM1 (most significant bit) to DataM4 (least significant bit) at its SDI port at the positive edge of SCK. SCK is initially at logic 0 Data release at the negative edge of SCK Data fetch at the positive edge of SCK
SCK
SS
Slave dispatches data from SDO port Master samples data at SDI port SDI
DataS1
DataS2
DataS3
DataS4
Master dispatches data from SDO port Slave samples data at SDI port SDO
DataM1
DataM2
DataM3
DataM4
Fig. 3.3 SPI bus protocol between a master and a single slave
There are four communication modes available for the SPI bus protocol. Each protocol is categorized according to the initial SCK level (the logic level at which SCK resides at steady state), and the data generation edge of the SCK. Each communication mode is shown in Fig. 3.4. MODE 0 communication protocol assumes that the steady state level for SCK is at logic 0. Each data bit is generated at the negative edge of SCK, and sampled at the positive edge by the master (or the slave). A good example of the MODE 0 protocol is shown in Fig. 3.3. MODE 1 still assumes the steady state level of SCK to be at logic 0, but the data generation takes place at the positive edge of SCK. Both the master and the slave read data at the negative edge in this mode. MODE 2 accepts the steady state level of SCK at logic 1. Data is released at the positive edge of SCK and is sampled at the negative edge as shown in Fig. 3.4. MODE 3 also accepts the steady state level of SCK at logic 1. However, data is released at the negative edge of SCK, and is sampled at the positive edge.
60
3
Serial Port
A master-slave pair must use the same mode during a data exchange. If multiple slaves are used, and each slave uses a different communication mode, the master has to reconfigure itself each time it communicates with a different slave. SPI bus has neither an acknowledgement mechanism to confirm receipt of data nor it offers any other data-flow control. In reality, an SPI bus master has no knowledge if a physical slave exists on the other side of the bus or data it sends is properly received by the slave. Most SPI implementations pack a byte of data in a clock burst which is eight clock periods long. However, many variants of SPI today use 16 or even 32 clock cycles to send more data bits in a burst in order to gain speed. MODE 0 SCK is initially at logic 0 Data release at the negative edge of SCK SCK
Data 1
DATA
Data 2
MODE 1 SCK is initially at logic 0 Data release at the positive edge of SCK SCK
DATA
Data 1
Data 2
Data 3
MODE 2 SCK is initially at logic 1 Data release at the positive edge of SCK SCK
Data 1
DATA
Data 2
MODE 3 SCK is initially at logic 1 Data release at the negative edge of SCK SCK
DATA
Fig. 3.4 SPI bus protocol modes
Data 1
Data 2
Data 3
3.2
3.2
Introduction to Inter Integrated Circuit (I2C)
61
Introduction to Inter Integrated Circuit (I2C)
Inter Integrated Circuit (I2C) is a multi-master bus protocol that transmits and receives data between bus masters and slaves using only two signal lines, Serial Clock (SCL) and Serial Data (SDA). Slave selection with slave select signals, address decoders or arbitrations is not necessary for this particular bus protocol. Any number of slaves and masters can be employed in an I2C bus using only two lines. The data rate is commonly at 100 Kbps which is the standard mode. However, the bus can operate as fast as 400 Kbps or even at 3.4 Mbps at high speed mode. Physically, the I2C bus consists of the two active wires, SDA and SCL, between a master and a slave device as shown in Fig. 3.5. The clock generation and data-flow are both bidirectional. This protocol assumes the device initiating the data transfer to be the bus master; all the other devices on the I2C bus are regarded as bus slaves. VDD
Rpu I2C Master SDA In
SCL Out
Rpu I2C Slave
SDA
SDA Out SCL In
VDD
SDA In SDA Out
SCL
SCL In SCL Out
Fig. 3.5 I2C architecture
In a typical I2C bus, both the bus master and the slave have two input ports, SCL In and SDA In, and two output ports, SCL Out and SDA Out, as shown in Fig. 3.5. When a master issues SCL Out ¼ 1 (or SDA Out ¼ 1), the corresponding n-channel MOSFET turns on, and pulls the SCL line (or SDA line) to ground. When the master issues SCL Out ¼ 0 (or SDA Out ¼ 0), it causes the corresponding n-channel transistor to turn off, resulting SCL (or SDA) afloat. However, neither SCL nor SDA is truly left floating at an undetermined voltage level. The pull-up resistor, Rpu, immediately lifts the floating line to the power supply voltage level, VDD. On the other side of the bus, the I2C slave detects the change at the SCL In (or SDA In) port, and determines the current bus value.
62
3
Serial Port
Each slave on the I2C bus is defined by an address field of either seven bits or ten bits as shown in Fig. 3.6. Each data packet following the address is eight bits long. There are only four control signals that regulate the data flow: Start, Stop, Write/ Read and Acknowledge. The seven-bit and ten-bit versions of read and write data transfers are shown in Fig. 3.6. The top sequence in this figure explains how a bus master writes multiple bytes of data to a slave that uses a seven-bit address. The master begins the sequence by generating a Start bit. This acts as a “wake-up” call to all the slave devices, and enables them to watch for the incoming address. This step is followed by a seven-bit long slave address. The bus master sends the most significant address bit first. The remaining address bits are released one bit at a time all the way to the least significant bit. At this point, all slave devices compare the bus address just sent out with their own addresses. If the address does not match, the slave simply ignores the rest of the incoming bits at the SDA bus, and waits for the beginning of the next bus transfer. If the addresses match, however, the addressed slave waits for the next bit that indicates the type of the transfer from the master. When the master sends out a Write bit, the slave responds with an acknowledge signal, SAck, by pulling the SDA line to ground. The master detects the acknowledge signal, and sends out the first eight-bit long data packet. The format for transmitting data bits is the same as the address: the most significant bit of the data packet is sent out first followed by the intermediate bits and the least significant bit. The slave produces another acknowledgement when all eight data bits are successfully received. The data delivery continues until the master completes sending all of its data packets. The transfer ends when the master generates a Stop signal. The second entry in Fig. 3.6 shows the write transfer to a bus slave whose address is ten bits long. Following the Start bit, the bus master sends out a five-bit preamble, 11110, indicating that it is about to send out a ten-bit slave address. Next, the master sends out the two most significant address bits followed by the Write bit. When the delivery of all these entries is acknowledged by the slave, the master sends out the remaining eight address bits until the least significant bit. This is followed by another slave acknowledgement, and the master transmitting all of its data bytes to the designated slave. The data transfer completes when the bus master generates a Stop bit. The third and the fourth entries in Fig. 3.6 show the seven-bit and ten-bit read sequences initiated by the bus master, respectively. In each sequence, after receiving the Start bit and the address, the designated slave starts sending out data packets to the master. After successfully receiving the first data byte, the master responds to the slave with an acknowledge signal, MAck, after which the slave transmits the next byte. The transfer continues until the slave delivers all of its data bytes to the master. However, right before the master issues a Stop bit, it generates a no-acknowledgement signal, MNack, signaling the end of the transfer as shown in Fig. 3.6.
3.2
Introduction to Inter Integrated Circuit (I2C)
Master = Write Addr Mode = 7 bits
63
Master response
Slave response
START
Slave Address
Write
SAck
Data1
SAck
DataN
SAck
STOP
1 bit
7 bits
1 bit
1 bit
8 bits
1 bit
8 bits
1 bit
1 bit
Master = Write Addr Mode = 10 bits
Master response
Slave response
START
11110
Slave Address
Write
SAck
Slave Address
SAck
Data1
SAck
DataN
SAck
STOP
1 bit
5 bits
msb (2 bits)
1 bit
1 bit
lsb (8 bits)
1 bit
8 bits
1 bit
8 bits
1 bit
1 bit
Master = Read Addr Mode = 7 bits
Master response
Slave response
START
Slave Address
Read
SAck
Data1
MAck
DataN
1 bit
7 bits
1 bit
1 bit
8 bits
1 bit
8 bits
Master = Read Addr Mode = 10 bits
Master response
MNack STOP 1 bit
1 bit
Slave response
START
11110
Slave Address
Read
SAck
Slave Address
SAck
Data1
MAck
DataN
1 bit
5 bits
msb (2 bits)
1 bit
1 bit
lsb (8 bits)
1 bit
8 bits
1 bit
8 bits
MNack STOP 1 bit
1 bit
Fig. 3.6 I2C modes of operation
The Start and Stop signals are generated by the combination of SCL and SDA values as shown in Fig. 3.7. According to this figure, a Start signal is produced when the SDA line is pulled to ground by the bus master while SCL ¼ 1. Similarly, a Stop signal is created when the bus master releases the SDA line while SCL ¼ 1. Fig. 3.7 I2C data stream start and stop conditions
SCL
SDA Start condition
Stop condition
Figure 3.8 shows when data is permitted to change, and when it needs to be steady. The I2C protocol only allows data changes when SCL is at logic 0. If the data on SDA changes while SCL is at logic 1, this may be interpreted as a Start or a Stop condition depending on the data transition. Therefore, the data on SDA is not allowed to change as long as SCL ¼ 1. Fig. 3.8 I2C data change conditions
SCL
SDA Data change is allowed
Data change is NOT allowed (Data is assumed VALID)
64
3
Serial Port
Figure 3.9 explains the timing diagram in which the bus master writes two bytes of data to a slave with a seven-bit address. According to this figure, the write process starts with transitioning the value at the SDA to logic 0 while SCL ¼ 1. Following the Start bit, the slave address bits are delivered sequentially from the most significant bit, SA[6], to the least significant bit, SA[0]. Each address bit is introduced to the SDA bus only when SCL is at logic 0 according to the I2C bus protocol shown in Fig. 3.8. The Write command and the subsequent slave acknowledgement are generated next. The data bits in Byte 0 and Byte 1 are also delivered to the SDA bus starting from the most significant data bit, D[7]. The write sequence completes when the SDA bus transitions to logic 1 while SCL ¼ 1. Master = Write Addr Mode = 7 bits SCL
SA[6]
SDA Start
SA[5]
D[7]
SA[0]
Slave Address
Write
D[6]
SAck
D[7]
D[0] Byte 0
SAck
D[6]
D[0]
Byte 1
SAck
Stop
Fig. 3.9 I2C write timing diagram
Figure 3.10 shows the timing diagram of reading two bytes of data from a slave. Following the Start bit and the slave address, the master issues the Read command by SDA ¼ 1. Subsequently, bytes of data are transferred from the slave to the master with the master acknowledging the delivery of each data byte. The transfer ends with the master not acknowledging the last byte of data, MNack, and generating the Stop bit. Master = Read Addr Mode = 7 bits SCL
SA[6]
SDA Start
SA[5] Slave Address
SA[0]
D[7] Read
SAck
D[6]
D[0] Byte 0
D[7] MAck
D[6] Byte 1
D[0] MNack Stop
Fig. 3.10 I2C read timing diagram
Here comes the reason why the I2C bus protocol excels in maintaining flawless communication between any number of masters and slaves using only two physical wires. For example, what happens if two or more devices are simultaneously trying to write data to the SDA bus? At the electrical level, there is actually no contention between multiple devices trying to simultaneously enter a logic level on the bus. If a particular device tries to write logic 0 to the bus while the other issues logic 1, then the physical bus structure with pull-up resistors in Fig. 3.5 ensures that there will be no electrical short or power drainage. Therefore, the bus actually transitions to logic 0. In other words, in any conflict, logic 0 always wins! This physical structure of the I2C bus also allows bus masters to be able to read values from the bus or write values onto the bus freely without any danger of collision. In case of a conflict between two masters (suppose one is trying to write
3.3
SPI interface
65
logic 0 and the other logic 1), the master that tries to write logic 0 gains the use of the bus without even being aware of the conflict. Only the master that tries to write logic 1 will know that it has lost the bus access because it reads logic 0 from the bus while trying to write logic 1. In most cases, this device will just delay its access, and try it later. Moreover, this bus protocol also helps dealing with communication problems. Any device present on the bus listens to the bus activity, particularly the presence of Start and Stop bits. Potential bus masters on the I2C bus, detecting a Start signal, will wait until they detect the Stop signal before attempting to access the bus. Similarly, unaddressed slaves go back to hibernation mode until the Stop bit is issued. Similarly, the master-slave pair is aware of each other’s presence by the activelow acknowledge bit after delivering each byte. If anything goes wrong, and the device sending the data does not detect acknowledgment from the recipient, the device sending data simply issues a Stop bit to stop the data transfer and releases the bus. An important element of the I2C communication is that the master device determines the clock speed in order to synchronize with the slave. If there are situations where an I2C slave device is not able to keep up with the master because the clock speed is too high, the master can lower the frequency by a mechanism referred to as “clock stretching”. According to this mechanism, an I2C slave device is allowed to hold the SCL at logic 0 if it needs the bus master to reduce the bus speed. The master is required to observe the SCL signal level at all times, and proceeds with the data transfer until the line is no longer pulled to logic 0 by the slave. All in all, both SPI and I2C offer good support for low speed peripheral communication. SPI is faster and better suited for single bus master applications where devices stream data to each other, while I2C is slower and better suited for multimaster applications. The two protocols offer the same level of robustness and have been equally successful among vendors producing Flash memories, Analog-toDigital and Digital-to-Analog converters, real-time clocks, sensors, liquid crystal display controllers etc.
3.3
SPI interface
The SPI master interface in dspic33fj128mc802 [1] has the capability of transmitting and sending serial data at alternating clock edges. Its simplified schematic is shown in Fig. 3.11. The SPI clock signal receives the CPU clock, FCY. However, this frequency can be divided by the primary and secondary clock dividers in the clock circuit to slow down FCY anywhere between 2 and 512 to produce the SPI clock, SCK. The SPI transmit function does not start until a prior data transfer is complete, and the shift register has no more valid data. The status register in this interface monitors this process, and when it finds the shift register empty it prompts the bus master to
66
3
Serial Port
send a new data packet to the SPI buffer. The data is first transferred to the SPI Buffer, then to the transmit buffer (SPI Tx Buffer) and finally to the shift register before it is serially shifted out of the SDO terminal at the negative edge of SCK. However, SPI interface is designed to receive serial data from the SDI terminal at the positive edge of SCK while transmitting valid bits from the SDO terminal. This received “junk” data is first transferred to the receive buffer (SPI Rx Buffer), then to the SPI buffer to be read by the bus master to complete the transmit operation. The SPI receive function also waits until the shift register is void of valid data. When empty, the bus master may employ sending a “junk” data packet to the SPI buffer to comply with the SPI data transfer protocol. The junk data is first transferred to the transmit buffer, and then to the shift register to be finally transmitted out of the SDO port. As this process takes place, valid data bits serially enter from the SDI port into the shift register. When the status register determines that the shift register is full with valid data, the data packet first moves to the receive buffer, then to the SPI buffer before being delivered to the bus master. Enable clock
1:1 to 1:8 Secondary Prescalar
SCK
SPICON[4:2]
Shift Control
SS
1:1 to 1:64 Primary Prescalar
15
SPICON[1:0]
0
SDO
SDI
SPI Shift Register
15
0
15
0
SPI Rx Buffer
SPI Tx Buffer
15
0 SPI Buffer
Read from SPI Buffer
16
Write to SPI Buffer
System bus
Fig. 3.11 Simplified dspic33fj128mc802 SPI interface
FCY
3.3
SPI interface
67
There are two registers that control the SPI interface. The first register, SPIxCON1, controls how the SPI interface functions, and determines its clock frequency as shown in Fig. 3.12. 15 14 13 -
-
-
12
11
10
9
8
7
6
5
DISSCK
DISSDO
MODE16
SMP
CKE
SSEN
CKP
MSTEN
4
3
2
1
0
SPRE[2:0] PPRE[1:0]
Fig. 3.12 SPIxCON1 register (x ¼ 1, 2)
The Disable SCK bit, DISSCK, disables the SCK output when it is equal to logic 1. When disabled, the physical SCK pin behaves like a digital I/O pin. Similarly, the Disable SDO bit, DISSDO, disables the SDO port when it is equal to logic 1. When disabled, this pin also functions like a digital I/O pin. The MODE16 bit determines the mode of the data transfer. Logic 1 entry makes the SPI unit operate with 16 bits. Otherwise, the SPI communication is limited to a byte. The Sample bit, SMP, determines where each data bit is sampled. Logic 1 entry samples the data bit at the next negative edge of SCK (remember - data is transmitted at each negative edge of SCK), and logic 0 entry samples the data at the positive SCK. The Clock Edge bit, CKE, determines the clock edge at which data change occurs. Logic 1 entry produces data output when SCK transitions from the active to the idle state. Logic 0 produces serial data when SCK transitions from the idle to the active state. The Slave Select Enable bit, SSEN, enables the active-low slave select bit, SS, when it is equal to logic 1. The Clock Polarity bit, CKP, determines the polarity of SCK, and complements the CKE bit discussed earlier. Logic 1 makes the idle state correspond to a high SCK level, and the active state to a low logic level. Logic 0 does the opposite: a high SCK level corresponds to the active state, and a low SCK level to the idle state. The Master Mode Enable bit, MSTEN, enables the master mode when it is equal to logic 1. The primary and secondary prescale bits, PPRE[1:0] and SPRE[2:0], are used for the division of the SPI clock, SCK. According to Table 3.1, the SCK frequency can be changed anywhere from FCY to FCY/512 with powers of 2.
68
3
Serial Port
Table 3.1 SPIxCON1 register Primary and Secondary Prescale entries PPRE[1:0] entry (Primary Prescale) in SPI1CON1 register 11 = Primary Prescale 1:1 10 = Primary Prescale 4:1 01 = Primary Prescale 16:1 00 = Primary Prescale 64:1
SPRE[2:0] entry (Secondary Prescale) in SPI1CON1 register 111 = Secondary Prescale 1:1 110 = Secondary Prescale 2:1 101 = Secondary Prescale 3:1 100 = Secondary Prescale 4:1 011 = Secondary Prescale 5:1 010 = Secondary Prescale 6:1 001 = Secondary Prescale 7:1 000 = Secondary Prescale 8:1
The second SPI register, SPIxSTAT, is a status register which enables the SPI interface, monitors its function and checks the status of its receive and transmit buffers during its operation as shown in Fig. 3.13. In this register, the SPI Enable bit, SPIEN, enables the SPI for transmit or receive operations. The SPI Stop at Idle bit, SPISIDL, discontinues the SPI operation when the device enters the idle mode. The SPI Receive Overflow bit, SPIROV, is a read-only bit, and it checks if the user software has read the previous data from the SPI buffer or not. Logic 1 indicates an overflow condition because the data from the buffer has not been completely read. Logic 0 indicates no overflow condition. The SPI Transmit Buffer Full bit, SPITBF, is also a read-only bit, and checks if the transmit buffer is full and ready to be read by the user software when it is equal to logic 1. Similarly, the SPI Receive Buffer Full bit, SPIRBF, checks if the receive buffer is full when it is equal to logic 1. 15
14
13
SPIEN
-
SPISIDL
12 11 10 -
-
-
9
8
7
-
-
-
Fig. 3.13 SPIxSTAT register (x ¼ 1, 2)
6 SPIROV
5
4
3
2
1
0
-
-
-
-
SPITBF
SPIRBF
3.3
SPI interface
69
The sample program in Program 3.1 shows the operation of the SPI interface [2]. Program 3.1 // Data transfer using SPI #include "xc.h" // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled #pragma config IESO = 0 #pragma config FNOSC = 7 #define PPSUnLock #define PPSLock
// start with a user-selected oscillator at reset // select FRC oscillator with postscalar at reset
__builtin_write_OSCCONL(OSCCON & 0xBF) __builtin_write_OSCCONL(OSCCON | 0x40)
unsigned char SPI_Transmit (unsigned char TxValue) { while (SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = TxValue; // When empty, send the byte to the TX buffer while (SPI1STATbits.SPIRBF == 0); // As valid bits shifts out of SDO, junk bits are received from SDI // Wait until the RX buffer is full of junk data return SPI1BUF; // When full, read the junk data in RX buffer through SPI1BUF } unsigned char SPI_Receive () { while(SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = 0x00; // When empty, send the junk byte (0x00) to the TX buffer while(SPI1STATbits.SPIRBF == 0); // As junk bits shifts out of SDO, valid bits are received from SDI // Wait until the RX buffer is full of valid data return SPI1BUF; // When full, read the valid data in RX buffer through SPI1BUF } void main() { // Clock source definition - A single FRC internal clock OSCTUNbits.TUN = 0; // select FRC = 7.37MHz CLKDIVbits.FRCDIV = 3; // FOSC = FRC/8 = 7.37MHz/8 and FP = FOSC/2 = 460KHz // SPI configuration MASTER mode sending 8 bits SPI1CON1bits.DISSCK = 0; // Enable the internal SPI clock SPI1CON1bits.DISSDO = 0; // Enable the SPI data output, SDO SPI1CON1bits.MODE16 = 0; // Enable the 8-bit data mode SPI1CON1bits.SSEN = 0; // This unit is not a slave so Slave Select pin is not used SPI1CON1bits.MSTEN = 1; // Enable MASTER mode SPI1CON1bits.SMP = 0; // Sample data at the pos edge when received at the neg edge of SCK
70
3
Serial Port
SPI1CON1bits.CKE = 1; // Output data changes when SCK goes from ACTIVE to IDLE state SPI1CON1bits.CKP = 0; // SCK polarity: IDLE is low phase and ACTIVE is high phase of SCK SPI1CON1bits.PPRE = 1; // Primary SPI clock pre-scale is 1:16 SPI1CON1bits.SPRE = 7; // Secondary SPI clock pre-scale is 1:1, thus SCK = 460/16 = 29KHz SPI1STATbits.SPIROV = 0; // Clear initial overflow bit in case an overflow condition in SPI1BUF SPI1STATbits.SPIEN = 1; // Enable the SPI interface // Peripheral Pin Select for RP pins PPSUnLock; RPOR4bits.RP8R = 8; // RP8 is an output for SCK1 RPINR20bits.SCK1R = 8; RPOR4bits.RP9R = 7; RPINR20bits.SDI1R = 7; RPOR3bits.RP6R = 9; PPSLock; // Define I/O TRISBbits.TRISB6 = 0; TRISBbits.TRISB8 = 0; TRISBbits.TRISB9 = 0; TRISBbits.TRISB7 = 1;
// RP8 is an input for SCK1 // RP9 is an output for SDO1 // RP7 is an input for SDI1 // RP6 is an output for SS1
// Configure RB6 as an output for SS1 // Configure RB8 as an output for SCK1 // Configure RB9 as an output SDO1 // Configure RB7 as an input for SDI1
// Application code goes here… }
This program is divided into four sections. The first section in Fig. 3.14 shows the oscillator configuration using pragma statements and programming the oscillator registers. Pragma statements program the FOSCSEL and FOSC registers that define the system clock. The first pragma statement, #pragma config FCKSM ¼ 3, turn off the clock switching and fail-safe modes. In the second pragma statement, we disable the OSCO terminal from being a crystal pin. Therefore, the statement, #pragma config OSCIOFNC ¼ 0, makes the OSCO terminal a general-purpose I/O pin. Since we do not intend to use any of the HS, XT or EC-type external crystals, we use the statement, #pragma config POSCMD ¼ 3, to disable all primary oscillators. Since only the internal FRC oscillator will be used to generate the system clock, the #pragma config IESO ¼ 0 statement disables any internal-to-external oscillator switch-over mechanism. The statement, #pragma config FNOSC ¼ 7, defines the active port in the 8-1 MUX in Fig. 1.3 when the system starts from reset. In this case, we select port 7 (or S7) because the internal FRC oscillator with FRCDIVN output will generate the system clock.
3.3
SPI interface
71
// Data transfer using SPI #include "xc.h" // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled #pragma config IESO = 0 #pragma config FNOSC = 7
// start with a user-selected oscillator at reset // select FRC oscillator with postscalar at reset
… void main() { // Clock source definition - A single FRC internal clock OSCTUNbits.TUN = 0; // select FRC = 7.37MHz CLKDIVbits.FRCDIV = 3; // FOSC = FRC/8 = 7.37MHz/8 and FP = FOSC/2 = 460KHz … }
Fig. 3.14 Oscillator configuration in Program 3.1
Programming the OSCTUN oscillator register determines the internal FRC clock frequency. The statement, OSCTUNbits.TUN ¼ 0, tunes the FRC clock at 7.37MHz. The CLKDIV register basically divides the clock frequency to define the CPU and peripheral clock frequencies. The last statement, CLKDIVbits.FRCDIV ¼ 3, divides the 7.37MHz FRC clock frequency by eight and produces 921.25KHz at the FOSC port and 460.62KHz at the FP port according to Fig. 1.3. The second section of this program configures the I/O pins as shown in Fig. 3.15. The Peripheral Pin Select (PPS) system configures the RPINR and RPOR registers to connect the physical chip pins to the input and output pins of the SPI1, respectively.
72
3
Serial Port
// Data transfer using SPI #include "xc.h" … #define PPSUnLock #define PPSLock
__builtin_write_OSCCONL(OSCCON & 0xBF) __builtin_write_OSCCONL(OSCCON | 0x40)
… void main() { … // Peripheral Pin Select with RP pins PPSUnLock; RPOR4bits.RP8R = 8; // RP8 is an output for SCK1 RPINR20bits.SCK1R = 8; // RP8 is an input for SCK1 RPOR4bits.RP9R = 7; // RP9 is an output for SDO1 RPINR20bits.SDI1R = 7; // RP7 is an input for SDI1 RPOR3bits.RP6R = 9; // RP6 is an output for SS1 PPSLock; // Define I/O TRISBbits.TRISB6 = 0; // Configure RB6 as an output for SS1 TRISBbits.TRISB8 = 0; // Configure RB8 as an output for SCK1 TRISBbits.TRISB9 = 0; // Configure RB9 as an output SDO1 TRISBbits.TRISB7 = 1; // Configure RB7 as an input for SDI1 // Application code goes here… }
Fig. 3.15 SPI I/O configuration in Program 3.1
According to Fig. 2.3 and Table 2.3, the SPI1 inputs, SCK1 and SDI1, are connected to the RP8 and RP7 pins using the RPINR20bits.SCK1R ¼ 8 and RPINR20bits.SDI1R ¼ 7 statements, respectively. Similarly, the SPI1 outputs, SCK1, SDO1 and SS1, are connected to the RP8, RP9 and RP6 pins, using the RPOR4bits.RP8R ¼ 8, RPOR4bits.RP9R ¼ 7 and RPOR3bits.RP6R ¼ 9 statements according to Fig. 2.5 and Table 2.4. Even though it is considered an output, the SCK1 port has to be assigned both as an input and output pin for the SPI1 unit to operate properly. The TRISB statements configure the digital I/O pins, RP6, RP8 and RP9, as output pins, and RP7 as an input pin.
3.3
SPI interface
73
The third section of the program shown in Fig. 3.16 configures the SPI interface. The SPI1CONbits.DISSCK ¼ 0 statement enables the SCK1 output. Similarly, the SPI1CONbits.DISSDO ¼ 0 statement enables the SDO port. The statement, SPI1CONbits.MODE16 ¼ 0, defines the data transfer is accomplished by eight-bit data packets. The SPI1CONbits.SSEN ¼ 0 statement removes the SS pin to be used as an input because this SPI1 interface is considered a master interface. The statement, SPI1CONbits.MSTEN ¼ 1, enables the master mode. The SPI1CONbits.SMP ¼ 0 statement samples the data at the positive edge of SCK1 when the data is delivered at the negative edge. The SPI1CONbits.CKE ¼ 1 statement forces the master to change data when SCK1 goes from active to idle state. In complementing this statement, the SPI1CONbits.CKP ¼ 0 statement defines the idle state as the lowphase, and the active state as the high-phase of SCK1. Therefore, these two instructions enable the master interface to deliver data at the negative edge of SCK1. The SPI1CONbits.PPRE ¼ 1 and SPI1CONbits.SPRE ¼ 7 statements define the primary and secondary pre-scale units not to engage in any SCK1 division. Therefore, the SCK1 clock frequency remains at 460KHz from the FP port as discussed in Fig. 3.14. // Data transfer using SPI #include "xc.h" ... void main() { … // SPI configuration MASTER mode sending 8 bits SPI1CON1bits.DISSCK = 0; // Enable the internal SPI clock SPI1CON1bits.DISSDO = 0; // Enable the SPI data output, SDO SPI1CON1bits.MODE16 = 0; // Enable the 8-bit data mode SPI1CON1bits.SSEN = 0; // This unit is not a slave so Slave Select pin is not used SPI1CON1bits.MSTEN = 1; // Enable MASTER mode SPI1CON1bits.SMP = 0; // Data is sampled at the pos edge when received at the neg edge of SCK SPI1CON1bits.CKE = 1; // SCK Edge: Data changes when SCK goes from ACTIVE to IDLE state SPI1CON1bits.CKP = 0; // SCK Polarity: IDLE is low phase and ACTIVE is high phase of SCK SPI1CON1bits.PPRE = 1; // Primary SPI clock pre-scale is 1:16 SPI1CON1bits.SPRE = 7; // Secondary SPI clock pre-scale is 1:1, thus SCK = 460/16 = 29KHz SPI1STATbits.SPIROV = 0; // Clear initial overflow bit in case an overflow condition in SPI1BUF SPI1STATbits.SPIEN = 1; // Enable the SPI interface … // Application code goes here… }
Fig. 3.16 SPI register configuration in Program 3.1
74
3
Serial Port
The statement regarding to the status register, SPI1STATbits.SPIROV ¼ 0, clears any overflow bit in the SPI1BUF register so the new data can be received and processed. The SPI1STATbits.SPIEN ¼ 1 statement simply enables the SPI master interface to deliver data to a selected slave. The fourth and final section of the program shown in Fig. 3.17 contains two function calls. The first function, SPI_Transmit, defines the data transmit, and the second function, SPI_Receive, outlines the data receive operation. The data transmit operation starts with checking the SPITBF bit of the status register to see if the SPI1 transmit buffer is empty using the while (SPI1STATbits. SPITBF ¼¼ 1) statement. If the transmit buffer is not empty, the while-statement forces the program to wait until it becomes empty. Next, the SPI1BUF ¼ TxValue statement sends a user data, TxValue, to the SPI1BUF, which is first transferred to the SPI1 Tx Buffer, and then to the shift register to be finally sent out of the SDO port. As the SPI1 unit sends data bits from the SDO port at the negative edge of SCK1, it also receives serial data from the SDI port to its shift register. However, these bits are considered invalid during transmit operation and considered junk. Therefore, when the program detects the shift register full of this junk data using the second while-statement, while (SPI1STATbits.SPIRBF ¼¼ 1), the contents of the shift register are first read to the SPI1 receive buffer, then to the SPI1 buffer to be disposed by the SPI1 master interface. // Data transfer using SPI #include "xc.h" ... unsigned char SPI_Transmit (unsigned char TxValue) { while (SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is em pty due to a prior process SPI1BUF = TxValue; // When empty, send the byte to the TX buffer while (SPI1STATbits.SPIRBF == 0); // As valid bits shifts out of the SDO port, junk bits are received from the SDI port // Wait until the RX buffer is full of junk data return SPI1BUF; // When full, read the junk data in RX buffer through SPI1BUF } unsigned char SPI_Receive () { while(SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = 0x00; // When empty, send the junk byte (0x00) to the TX buffer while(SPI1STATbits.SPIRBF == 0); // As junk bits shifts out of the SDO port, valid bits are received from the SDI port // Wait until the RX buffer is full of valid data return SPI1BUF; / / When full, read the valid data in RX buffer through SPI1BUF } void main() { … // Application code goes here… }
Fig. 3.17 SPI transmit and receive function calls in Program 3.1
References
75
The data receive operation is described in the second function. Again, the while (SPI1STATbits.SPITBF ¼¼ 1) statement checks to see if the transmit buffer is empty, and waits until it becomes empty. The SPI1BUF ¼ 0x00 statement sends a junk byte, 0x00, to the SPI1 transmit buffer since the contents of this buffer, or the serial bits from the SDO port is immaterial for the receive operation. As junk bits are shifted out, valid bits are received to the shift register from the SDI port. The statement, while (SPI1STATbits.SPIRBF ¼¼ 0), checks the SPIRBF bit in the status register, and waits until the shift register is full of valid data. When full, this data is promptly transferred to the SPI1 receive buffer, and then to the SP1BUF to be read by the SPI1 master interface. Projects 1. Write a program that assumes one master and three slave devices as shown in Fig. 3.2. Redefine the I/O description in Fig. 3.15 but use the same Transmit and Receive functions in Fig. 3.17 2. Program the SPI registers in Fig. 3.16 for a bus master such that it operates in 16-bit data mode and delivers data at the positive edge of SCK. 3. Program the SPI registers in Fig. 3.16 for a bus slave such that it also operates in 16-bit data mode and receives data at the negative edge of SCK. 4. Form Transmit and receive functions for I2C interface. 5. Program the I2C registers for a bus master that operates in 7-bit address mode, and writes data at the negative edge of SCL. 6. Program the I2C registers for a bus master that operates in 7-bit address mode, but reads data from a slave at the positive edge of SCL.
References 1. dspic33fj128mc802/804 Microchip processor datasheet, https://www.microchip.com/en-us/ product/dsPIC33FJ128MC802, Accessed January 2023. 2. MPLABX software design environment, https://www.microchip.com/en-us/tools-resources/ develop/mplab-x-ide, Accessed January 2023.
Chapter 4
External Memories
4.1
Synchronous Random Access Memory (SRAM)
There are times when temporary data needs to be stored in an external memory due to space limitations in the CPU’s data memory. In such an instance, SRAM is the first memory type to consider. The Integrated Circuit (IC) that houses SRAM can be connected to dspic33fj128mc802’s parallel port, RB[15:0], or it can be serially connected to the processor’s dedicated SPI or I2C ports [1]. A typical SRAM architecture shown in Fig. 4.1 is composed of four different blocks: the SRAM core, the address decoder, the sense amplifier and the internal SRAM controller. The memory core retains all immediate data. The sense amplifier amplifies the cell voltage to full logic levels during a read cycle. The address decoder generates all 2N Word Lines (WL) from an N-bit wide address. In this figure, a 16-bit address produces 216 = 65536 word lines. The controller generates self-timed pulses to guide the data from or to the memory core during a read or write cycle, respectively. Each SRAM cell is composed of two back-to-back inverters that form a latch, and two N-channel Metal Oxide Semiconductor (NMOS) pass-gate transistors to isolate the existing data in the cell or allow new data as shown in Fig. 4.2. When data needs to be written into a cell, WL = 1 turns on both NMOS transistors, allowing the true and complementary data to be simultaneously written to the cell from the Bit and Bitbar inputs. When WL = 0, both NMOS transistors turn off, and the latch becomes completely isolated from its surroundings. As a result, prior logic level is contained in the cell. Similarly, if the data needs to be read from the cell, both NMOS transistors are turned on by WL = 1, and the small differential potential developed between the Bit and Bitbar outputs is amplified by a sense amplifier to reach a full logic level at the SRAM output.
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 A. Bindal, Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers, https://doi.org/10.1007/978-3-031-27841-9_4
77
78
4 7
External Memories 0
65535
16 Address[15:0]
Address Decoder
SRAM Core
65536 WL[65,535:0]
Precharge EnWL
8
WritePulse
DataIn[7:0]
0
8 EN WE
Sense Amplifier
Internal SRAM Controller
ReadPulse
DataOut[7:0]
Fig. 4.1 Typical SRAM architecture with 16-bit address and eight-bit data Fig. 4.2 SRAM memory cell
WL
Bit
A
B
Bitbar
The data write sequence starts with EN (Enable) = 1 and Write Enable (WE) = 1. This combination precharges the Bit and Bitbar nodes in the SRAM core and raises them to a preset value. When the precharge cycle is complete, the controller enables the address decoder by EnWL = 1 as shown in Fig. 4.1. The address decoder activates one single WL input out of 256 WLs according to the value provided at Address[15:0] input. Within the same time period, the controller also produces WritePulse = 1, which allows the valid data at DataIn[7:0] to be written to a specified address.
4.1
Synchronous Random Access Memory (SRAM)
79
Reading data from the SRAM core is performed by EN = 1 and WE = 0. Similar to the write operation, the controller first precharges the SRAM core prior to reading data, and then turns on the address decoder. According to the address value at Address[15:0] port, the WL input to a specific row is activated, and the data is read from each cell to the Bit and Bitbar nodes at this row. The sense amplifier amplifies the cell voltage between Bit and Bitbar nodes to full logic levels and delivers the data to DataOut[7:0] port. The SRAM read and write sequences are shown in Figs. 4.3 and 4.4, respectively. In Fig. 4.3, when EN = 1 and WE = 0, SRAM is enabled to operate in the read mode. The core delivers valid data sometime after the next positive edge of the clock. In Fig. 4.4, when EN and WE inputs are raised to logic 1, SRAM goes into the write mode, and the valid data is written to a specified address at the next positive clock edge. clock
WE
EN
Valid Address
Address[15:0]
DataOut[7:0]
Valid Data
Fig. 4.3 SRAM I/O timing for read
clock
WE
EN
Address[15:0]
DataIn[7:0]
Fig. 4.4 SRAM I/O timing for write
Valid Address
Valid Data
80
4
External Memories
However, the SRAM architecture in Fig. 4.1 that uses 16-bit address and eight-bit unidirectional data ports can be converted into a form that uses SPI or I2C serial bus protocols as shown in the block diagram in Fig. 4.5. The serial bus interface converts serial address and data entries through the SDA port into parallel address and data when the data transmission follows the I2C protocol. If the transmission is the SPI type, then the serial data received at the SDI port is parallelized before it is delivered to the SRAM’s DataIn port, and the parallel data from the DataOut port is serialized before it is sent out from the SDO port.
SS
DataIn[7:0] 8
SPI
SCK
DataOut[7:0] 8
SDI
Address[15:0] 16
SDO
Serial Bus Interface
SCL
WE
SDA
EN
I2C
SRAM Fig. 4.5 Generic SRAM module SPI and I2C serial interfaces
To demonstrate data exchange with an SRAM, we chose a 64 KB Microchip SRAM module, 23K640, with SPI interface [2]. There are four commands for this module as shown in Fig. 4.6. Fig. 4.6 Microchip 23K640 instruction set
INSRUCTION SET 0000 0011 = Read data from an address 0000 0010 = Write data to an address 0000 0101 = Read status register 0000 0001 = Write status register
The read command, 0x03, is sent to the SRAM module at the negative edge of SCK followed by a 16-bit address through its SI port (serial data input – SDI) as shown in Fig. 4.7. The module responds this query by delivering an eight-bit data from its SO port (serial data output – SDO). The read transaction is achieved while the active-low chip-select signal,CS (equivalent of slave select -SS), is at logic 0 as shown in this figure.
4.1
Synchronous Random Access Memory (SRAM)
81
CS SCK SI
0
0
0
0
0
0
1
1
15
14
0
SO
7
Read Command = 0x03
6
5
Address[15:0]
4
3
2
1
0
Data[7:0]
Fig. 4.7 Microchip 23K640 read sequence
Similarly the write sequence starts with the write command, 0x02. This is followed by a 16-bit address to the SI port as shown in Fig. 4.8. Finally, the data byte is written to the module with its most significant bit first. As in the read case, the write transaction is also achieved while the active-low chip-select signal,CS, is at logic 0. CS SCK SI
0
0
0
0
0
0
1
0
15
14
0
7
6
5
4
3
2
1
0
SO
Write Command = 0x02
Address[15:0]
Data[7:0]
Fig. 4.8 Microchip 23K640 write sequence
The two other commands are the read and write commands to the SRAM’s status register as shown in Fig. 4.9. The status register also shown in this figure defines the mode of a serial data transfer. The byte mode, 00, prompts single bytes to be written to SRAM or read. The page mode, 10, writes or reads the contents of the entire page by incrementing an internal address counter. The initial address specifies the beginning of each 32 byte page, and there are 1024 such pages in 23K640. The sequential mode, 01, allows an entire array or data to be written or read. Again, an internal address counter generates the SRAM address until the entire array data is finished. Page boundaries are ignored in sequential mode. The only way to suspend the data transfer process while the device is in sequential or page mode is to issue an active-low hold signal, HOLD. Fig. 4.9 Microchip 23K640 status register
7
6
5
4
3
2
1
0
MODE
MODE
0
0
0
0
1
HOLD
MODE[1:0]
MODE[1:0] entry in STATUS register 11 = Reserved 10 = Page mode 01 = Sequential mode 00 = Byte mode (default)
82
4
External Memories
Program 4.1 demonstrates how to write a byte of data to a 64 KB Microchip serial SRAM, 23K640, and then read from the same address [3]. Program 4.1 // MCU programs the 64K Microchip serial SRAM with 1 byte using SPI #include "xc.h" // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3
// primary oscillators are disabled
// Configuration Register - FOSCSEL #pragma config IESO = 0 #pragma config FNOSC = 7
// start with a user-selected oscillator at reset // select FRC oscillator with postscalar at reset
// Configuration Register - FICD #pragma config JTAGEN = 0
//JTAG is disabled
#define PPSUnLock
__builtin_write_OSCCONL(OSCCON & 0xBF)
#define PPSLock
__builtin_write_OSCCONL(OSCCON | 0x40)
unsigned char SPI_Transmit (unsigned char TxValue) { while (SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = TxValue; // When empty, send the byte to the TX buffer while (SPI1STATbits.SPIRBF == 0); // As valid bits shifts out of SDO, junk bits are received from SDI return SPI1BUF;
// Wait until the RX buffer is full of junk data // When full, read the junk data in RX buffer from SPI1BUF
} unsigned char SPI_Receive () { while(SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = 0x00; // When empty, send the junk byte (0x00) to the TX buffer while(SPI1STATbits.SPIRBF == 0); // As junk bits shifts out of SDO, valid bits are received from SDI return SPI1BUF;
// Wait until the RX buffer is full of valid data // When full, read the valid data in RX buffer from SPI1BUF
} void Write_Status_Reg (unsigned char WriteStatusByte) { LATBbits.LATB6 = 0;
// Lower SS for instruction delivery
SPI_Transmit (0x01); SPI_Transmit (WriteStatusByte);
// Send the Write status register instruction // Send a byte to status register
LATBbits.LATB6 = 1; }
// Raise SS for instruction completion
4.1
Synchronous Random Access Memory (SRAM)
83
unsigned char Read_Status_Reg () { LATBbits.LATB6 = 0;
// Lower SS for instruction delivery
SPI_Transmit (0x05); // Send the Read status register instruction unsigned char status = SPI_Receive (); // Read the status register LATBbits.LATB6 = 1; return status;
// Raise SS for instruction completion
} void Write_Byte (unsigned int Address, unsigned char SentByte) { LATBbits.LATB6 = 0;
// Lower SS to start PP
SPI_Transmit (0x02); // Send the byte program instruction SPI_Transmit((Address >> 8) & 0xFF); // Send the MSB of the 16-bit address SPI_Transmit (Address & 0xFF);
// Send the LSB of the 16-bit address
SPI_Transmit (SentByte); LATBbits.LATB6 = 1;
// Raise SS for instruction completion
} unsigned char Read_Byte (unsigned int Address) { LATBbits.LATB6 = 0;
// Lower SS to start PP
SPI_Transmit (0x03); // Send the PP instruction SPI_Transmit((Address >> 8) & 0xFF); // Send the MSB of the 16-bit address SPI_Transmit (Address & 0xFF); // Send the LSB of the 16-bit address unsigned char ReceivedByte = SPI_Receive (); LATBbits.LATB6 = 1; return ReceivedByte;
// Raise SS for instruction completion
} void main() { // Clock source definition - A single FRC internal clock // OSCTUN Register OSCTUNbits.TUN = 0;
// select FRC = 7.37MHz
// CLKDIV Register CLKDIVbits.FRCDIV = 3;
// FOSC = FRC/8 = 7.37MHz/8 and FP = FOSC/2 = 460KHz
// SPI configuration MASTER mode sending 8 bits SPI1CON1bits.DISSCK = 0; // Enable the internal SPI clock SPI1CON1bits.DISSDO = 0; // Enable the SPI data output, SDO SPI1CON1bits.MODE16 = 0; // Enable the 8-bit data mode SPI1CON1bits.SSEN = 0; // This unit is not a slave so Slave Select pin is not used SPI1CON1bits.MSTEN = 1; // Enable MASTER mode SPI1CON1bits.SMP = 0;
// Sample data at the pos edge when received at the neg edge of SCK
84
4
External Memories
// Peripheral Pin Select with RP pins PPSUnLock; RPOR4bits.RP8R = 8;
// RP8 is an output for SCK1
RPINR20bits.SCK1R = 8; RPOR4bits.RP9R = 7;
// RP8 is an input for SCK1 // RP9 is an output for SDO1
RPINR20bits.SDI1R = 7; RPOR3bits.RP6R = 9;
// RP7 is an input for SDI1 // RP6 is an output for SS1
PPSLock; // Define I/O TRISBbits.TRISB6 = 0; TRISBbits.TRISB8 = 0;
// Configure RB6 as an output for SS // Configure RB8 as an output for SCK1
TRISBbits.TRISB9 = 0; TRISBbits.TRISB7 = 1;
// Configure RB9 as an output SDO1 // Configure RB7 as an input for SDI1
// Issue Enable Write Status Register command to write mode 0 and no hold Write_Status_Reg (0x01); while ((Read_Status_Reg () & 0xC1) == 0x03) // While Mode and HOLD pins are different // keep reading the status register Read_Status_Reg (); // Write 0xAA at address 0x0000 Write_Byte (0x0000, 0xAA); // When WEL = 1, send write instruction to write 0xAA at address 0x0000 // Read 0xAA from address 0x0000 Read_Byte (0x0000);
// When BUSY = 0 read the byte back from address 0x0000
}
The SRAM module has five ports to communicate with a master interface: SCK, SI, SO, CS, HOLD. The SCK is the clock port where SRAM receives the master clock. The SI and SO ports are the input and output data ports, and must be connected to SDO and SDI master ports, respectively. The CS port is the chip select port, and connects to the SS port from a bus master. Lastly, the HOLD port is an active-low enable port that suspends the data transfer. The oscillator registers that control the SPI clock interface on dspic33fj128mc802 are shown in Fig. 4.10. The pragma statements program the configuration register of the oscillator to define the type of the oscillator used in this chip. We use a single internal FRC clock. Therefore, any clock switch-over mechanism that starts with a low frequency internal clock and switches over to a higher frequency clock is disabled. Therefore, the clock mode and switch-over statements such as #pragma config FCKSM = 3 and #pragma config IESO = 0 disable both the clock switching and fail-safe modes. Since external clock is not used in the system, the statement, #pragma config OSCIOFNC = 0, removes the OSCO pin from being used as an external crystal pin. Finally, the statement, #pragma config FNOSC = 7, selects the internal FRC oscillator out of eight different possible oscillator configurations.
4.1
Synchronous Random Access Memory (SRAM)
85
// MCU programs the 64K Microchip serial SRAM with 1 byte using SPI #include "xc.h" // Configuration Register - FOSC #pragma config FCKSM = 3 #pragma config OSCIOFNC = 0 #pragma config POSCMD = 3
// both clock switching and fail-safe modes are disabled // OSCO pin is a general purpose I/O pin // primary oscillators are disabled
// Configuration Register - FOSCSEL #pragma config IESO = 0 #pragma config FNOSC = 7
// start with a user-selected oscillator at reset // select FRC oscillator with postscalar at reset
… void main() { // Clock source definition - A single FRC internal clock // OSCTUN Register OSCTUNbits.TUN = 0; // select FRC = 7.37MHz // CLKDIV Register CLKDIVbits.FRCDIV = 3;
// FOSC = FRC/8 = 7.37MHz/8 and FP = FOSC/2 = 460KHz
… }
Fig. 4.10 Configuration of the oscillator for SPI interface
The OSCTUN and CLKDIV registers define the clock frequency. In this case, the internal FRC clock frequency is selected to be 7.37 MHz. However, a post-scalar clock divider divides the initial clock by eight, and produces 921 KHz at the FOSC and approximately 460 KHz at the FP oscillator outputs. The reader should consult Chap. 1 of this book or section 9.0 of dspic33fj128mc802 datasheet to be more familiar with the selection and configuration of oscillators. Figure 4.11 shows the segment of Program 4.1 that configures the SPI I/O ports for the bus master. According to this figure, RP6 is configured as an output pin by TRISBbits.TRISB6 = 0, and assigned to be the active-low slave select pin, SS1, by the statement, ROPR3bits.RP6R = 9. RP8 is also configured as an output by TRISBbits.TRISB8 = 0, but assigned to operate both as an output and input by the ROPR4bits.RP8R = 8 and RPINR20bits.SCK1R = 8 statements, respectively. RP9 is configured as an output by the statement, TRISBbits.TRISB9 = 0, and assigned to SDO1 by the statement, ROPR4bits.RP9R = 7. Finally, RP7 is configured as an input by the TRISBbits.TRISB7 = 1 statement, and assigned to SDI1 by the RPINR20bits.SDI1R = 7 statement.
86
4
External Memories
// MCU programs the 64K Microchip serial SRAM with 1 byte using SPI #include "xc.h" … #define PPSUnLock #define PPSLock
__builtin_write_OSCCONL(OSCCON & 0xBF) __builtin_write_OSCCONL(OSCCON | 0x40)
void main() { … // Peripheral Pin Select with RP pins PPSUnLock; RPOR4bits.RP8R = 8; // RP8 is an output for SCK1 RPINR20bits.SCK1R = 8; // RP8 is an input for SCK1 RPOR4bits.RP9R = 7; // RP9 is an output for SDO1 RPINR20bits.SDI1R = 7; // RP7 is an input for SDI1 RPOR3bits.RP6R = 9; // RP6 is an output for SS1 PPSLock; // Define I/O TRISBbits.TRISB6 = 0; // Configure RB6 as an output for SS TRISBbits.TRISB8 = 0; // Configure RB8 as an output for SCK1 TRISBbits.TRISB9 = 0; // Configure RB9 as an output SDO1 TRISBbits.TRISB7 = 1; // Configure RB7 as an input for SDI1 … }
Fig. 4.11 Definition of the chip I/O for reading and writing data packets
Configuration of the SPI interface for the bus master is shown in Fig. 4.12. The SPI1CONbits.DISSCK = 0 and SPI1CONbits.DISSDO = 0 statements enable the SCK1 and SDO1 outputs, respectively. The statement, SPI1CONbits.MODE16 = 0, defines a data transfer composed of eight-bit data packets. The SPI1CONbits. MSTEN = 1 statement enables the SPI1 interface to be in master mode, and since it is considered a master interface the SPI1CONbits.SSEN = 0 statement removes the SS pin to be used as an input. The SPI1CONbits.SMP = 0 statement samples the data in the middle, which translates data sampling at the positive edge of SCK1 when the data is delivered at the negative edge. The SPI1CONbits.CKE = 1 statement forces the master to change data when SCK1 goes from active to idle state, and the SPI1CONbits.CKP = 0 statement defines the idle state as the low-phase, and
4.1
Synchronous Random Access Memory (SRAM)
87
the active state as the high-phase of SCK1. The combination of these two instructions enables the master interface to produce data at SDO1 at the negative edge of SCK1. The statements, SPI1CONbits.PPRE = 1 and SPI1CONbits.SPRE = 7, skip any division of SCK1 by the primary and secondary pre-scale units. As a result, the SCK1 clock frequency remains at FP = 460 KHz. The statement, SPI1STATbits.SPIROV = 0, clears any overflow bit in the SPI1BUF register in order to receive and process new data immediately. The statement, SPI1STATbits.SPIEN = 1, simply enables the SPI master interface and starts the operation. // MCU programs the 64K Microchip serial SRAM with 1 byte using SPI #include "xc.h" … void main() { … // SPI configuration MASTER mode sending 8 bits SPI1CON1bits.DISSCK = 0; // Enable the internal SPI clock SPI1CON1bits.DISSDO = 0; // Enable the SPI data output, SDO SPI1CON1bits.MODE16 = 0; // Enable the 8-bit data mode SPI1CON1bits.SSEN = 0; // This unit is not a slave so Slave Select pin is not used SPI1CON1bits.MSTEN = 1; // Enable MASTER mode // Sample data at the pos edge when received at the neg edge of SPI1CON1bits.SMP = 0; SCK SPI1CON1bits.CKE = 1; // SCK edge: Output data changes when SCK goes from ACTIVE to IDLE state SPI1CON1bits.CKP = 0; // SCK polarity: IDLE is low phase and ACTIVE is high phase of SCK SPI1CON1bits.PPRE = 1; // Primary SPI clock pre-scale is 1:1 SPI1CON1bits.SPRE = 7; // Secondary SPI clock pre-scale is 1:1 -> SCK = 460KHz SPI1STATbits.SPIROV = 0; // Cle ar initial overflow bit in case an overflow condition in SPI1BUF SPI1STATbits.SPIEN = 1; // Enable the SPI interface … }
Fig. 4.12 Configuration of the SPI interface for reading and writing data packets
Figure 4.13 shows the first set of function calls, SPI_Receive and SPI_Transmit, used in Program 4.1. These primary functions define how the master SPI1 interface receives and transmits serial data using the SPIRBF and SPITBF bits in the SPI status register described in Chap. 3. Junk data received during a transmit cycle, or valid data received during a receive cycle is handled by the bus master, reading the contents of SPI1BUF by the statement, return SPI1BUF.
88
4
External Memories
// MCU programs the 64K Microchip serial SRAM with 1 byte using SPI #include "xc.h" … unsigned char SPI_Transmit (unsigned char TxValue) { while (SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = TxValue; // When empty, send the byte to the TX buffer while (SPI1STATbits.SPIRBF == 0); // As valid bits shifts out of the SDO port, junk bits are received from the SDI port // Wait until the RX buffer is full of junk data return SPI1BUF; // When full, read the junk data in RX buffer through SPI1BUF } unsigned char SPI_Receive () { while(SPI1STATbits.SPITBF == 1); SPI1BUF = 0x00; while(SPI1STATbits.SPIRBF == 0); port return SPI1BUF; }
// Wait until the TX buffer is empty due to a prior process // When empty, send the junk byte (0x00) to the TX buffer // As junk bits shifts out of the SDO port, valid bits are received from the SDI // Wait until the RX buffer is full of valid data // When full, read the valid data in RX buffer through SPI1BUF
… void main() { … }
Fig. 4.13 Transmit and receive function calls
Figure 4.14 shows the secondary function calls responsible for writing and reading serial data to and from the SRAM module, respectively. All four functions in this figure include the SPI_Transmit and SPI_Receive primary functions. The statements in each function are shown by lowering and raising the active-low SS pin, LATBbits.LATB6 = 0 and LATBbits.LATB6 = 1, to start and stop serial data transfer, respectively. The first function, Write_Status_Reg, writes an eight-bit user data, WriteStatusByte, to the SRAM’s status register. After lowering RP6 to logic 0 by LATBbits.LATB6 = 0, the function, SPI_Transmit (0x01), sends the write command to program the SRAM’s status register. The function, SPI_Transmit (WriteStatusByte), sends a user byte, WriteStatusByte, to be written to the status register. The function call ends with the LATBbits.LATB6 = 1 statement, which raises RP6 to logic 1 and deselects the SRAM module. The second function, Read_Status_Reg, reads the contents of the SRAM’s status register. The read command, 0x05, is sent to the SRAM module by SPI_Transmit (0x05). This is followed by the SPI_Receive function which reads the contents of the SRAM’s status register to an auxiliary register called “status”. When the read
4.1
Synchronous Random Access Memory (SRAM)
89
process is complete, RP6 is raised to logic 1, and the contents of the auxiliary register are read by the statement, return status. The third function, Write_Byte, defines the actual write process to the SRAM module. The first statement in this function, SPI_Transmit (0x02), sends the byte program command, 0x02, to the SRAM’s SPI interface. The second and third statements form a 16-bit SRAM address, and sent serially one after the other to the SRAM module. The statement, SPI_Transmit ((Address >> 8) & 0xFF), shifts the 16-bit SRAM address to the right by eight bits to produce the most significant byte while the third statement, SPI_Transmit (Address & 0xFF), forms the least significant address byte. The write process ends with the SPI_Transmit (SentByte) statement where a user data byte, SentByte, is sent to SRAM. The fourth function, Read_Byte, defines the read process from the SRAM module. The read command, 0x03, is sent by the SPI_Transmit (0x03) statement. This is followed by the statements, SPI_Transmit ((Address >> 8) & 0xFF) and SPI_Transmit (Address & 0xFF), in order to serially send the most and the least significant address bytes. The SPI_Receive function in the fourth statement receives a byte of data from a designated SRAM address and stores it in an auxiliary register, ReceivedByte. The contents of the auxiliary register are consequently read by the bus master to terminate the read process. // MCU programs the 64K Microchip serial SRAM with 1 byte using SPI #include "xc.h" … void Write_Status_Reg (unsigned char WriteStatusByte) { LATBbits.LATB6 = 0; // Lower SS for instruction delivery SPI_Transmit (0x01); // Send the Write status register instruction SPI_Transmit (WriteStatusByte); // Send a byte to status register LATBbits.LATB6 = 1; // Raise SS for instruction completion } unsigned char Read_Status_Reg () { LATBbits.LATB6 = 0; SPI_Transmit (0x05); unsigned char status = SPI_Receive (); LATBbits.LATB6 = 1; return status; }
// Lower SS for instruction delivery // Send the Read status register instruction // Read the status register // Raise SS for instruction completion
void Write_Byte (unsigned int Address, unsigned char SentByte) { LATBbits.LATB6 = 0; // Lower SS to start PP
Fig. 4.14 Status, read and write function calls
90
4
External Memories
SPI_Transmit (0x02); // Send the byte program instruction SPI_Transmit ((Address >> 8) & 0xFF); // Send the MSB of the 16-bit address SPI_Transmit (Address & 0xFF); // Send the LSB of the 16-bit address SPI_Transmit (SentByte); LATBbits.LATB6 = 1; // Raise SS for instruction completion } unsigned char Read_Byte (unsigned int Address) { LATBbits.LATB6 = 0; // Lower SS to start PP SPI_Transmit (0x03); // Send the PP instruction SPI_Transmit ((Address >> 8) & 0xFF); // Send the MSB of the 16-bit address SPI_Transmit (Address & 0xFF); // Send the LSB of the 16-bit address unsigned char ReceivedByte = SPI_Receive (); LATBbits.LATB6 = 1; // Raise SS for instruction completion return ReceivedByte; } void main() { … }
Fig. 4.14 (continued)
Figure 4.15 shows how to write a byte of data to an SRAM address, and read it from the same address. The first task is to define the mode of operation. This can be determined by the most significant two bits of the SRAM status register. Since we will be writing only one byte of data (and read one byte also), we select Mode = 00 according to Fig. 4.9. We also disable any hold operation by HOLD = 1. This entry can be accomplished if 0x01 is written to the status register as shown in the first function call, Write_Status_Reg (0x01). Next, we need to verify if the process of writing 0x01 to the status register is complete. This is accomplished by the statement, while ((Read_Status_Reg () & 0xC1) == 0x03). This statement waits until the contents of the status register actually becomes equal to 0x03, an indication that shows the SRAM is in byte mode. That way, the user programs the memory one byte at a time, and reads the same way with the hold option disabled. Otherwise the program keeps reading the contents of the status register by the Read_Status_Reg () function call. Once programming SRAM’s status register is complete, then the user can attempt to write a single byte to an arbitrary SRAM address. The Write_Byte (0x0000, 0xAA) function in Fig. 4.15 writes a data byte, 0xAA, to the address, 0x0000. The data, 0xAA, is subsequently read back from the address 0x0000 by another function call, Read_Byte (0x0000).
4.2
Flash Memory
91
// MCU programs the 64K Microchip serial SRAM with 1 byte using SPI #include "xc.h" … void main() { … // Issue Enable Write Status Register command to write mode 0 and no hold Write_Status_Reg (0x01); while ((Read_Status_Reg () & 0xC1) == 0x03) // While Mode and HOLD pins are different keep reading the status register Read_Status_Reg (); // Write 0xAA at address 0x0000 Write_Byte (0x0000, 0xAA);
// Send write instruction to write 0xAA at address 0x0000
// Read 0xAA from address 0x0000 Read_Byte (0x0000);
// Read the byte back from address 0x0000
}
Fig. 4.15 SRAM operation using SPI
4.2
Flash Memory
The Flash memory cell shown in Fig. 4.16 is basically an N-channel MOS transistor with an additional floating gate layer sandwiched between its control gate terminal (Wordline) and the channel where the electronic conduction takes place. This device has also drain (Bitline) and source (Sourceline) terminals for connecting the cell to the neighboring circuitry. To write logic 0 into the memory cell, a high voltage is applied between Wordline and Bitline terminals while the Sourceline node remains connected to ground. This configuration generates hot carriers in the transistor channel which tunnel through the gate oxide and reach the floating gate, raising the threshold voltage of the transistor. The raised threshold voltage prevents the programmed device to be turned on by the standard gate-source voltage used during normal circuit operations, and causes the value stored in the device to be interpreted as logic 0. An unprogrammed device with no charge on the floating gate, on the other hand, exhibits low threshold voltage characteristics, and can be turned on by the standard gate-source voltage, producing current in its channel. In this state, the value stored in the device is interpreted as logic 1. Fig. 4.16 Flash memory cell
Wordline Floating gate Sourceline
Bitline Channel
92
4
External Memories
Flash memory is the successor of the Electrically-Erasable-Programmable-ReadOnly-Memory (EEPROM or E2PROM), and as its predecessor it has the capability of retaining data after the power is turned off. Therefore, it is ideal to use in handheld computers, cell phones and other mobile platforms. A typical Flash memory is composed of multiple sectors and pages as shown in Fig. 4.17. An eight-bit word can be located in a Flash memory by specifying the sector, the page and the row addresses. This particular Flash memory contains 16 sectors and 16 pages. Each page contains 256 bytes. The sector address constitutes the most significant four bits of the 16-bit Flash address, namely Addr[15:12]. Each page in a sector is addressed by Addr[11:8], and each byte in a page is addressed by Addr[7: 0]. There are five main control signals in a Flash memory to perform basic read, write (program), erase, protect and reset operations. Write and program commands are equivalent to each other, and used interchangeably throughout the manuscript when describing Flash memory operations. Many Flash datasheets use the term, program, to define writing a byte or a block of data to Flash memory. The active-low Enable input, EN, activates a particular page in the Flash memory to prepare it for an upcoming operation. The active-low Read Enable input, RE, activates the Read/Write interface to read data from the memory. The active-low Write Enable input, WE, enables to write (program) data to the memory. The activelow Reset input, Reset, is used for resetting the hardware after which the Flash memory automatically goes into the read mode.
Addr[23:8] = Addr[23:19] = Sector[4:0] Addr[18:8] = Page[10:0] 8
Sector 31
7
Sector 0
7
0
0 255
255 8
Reset 0
0
Flash Read/Write Interface 8 I/O[7:0]
Fig. 4.17 Generic Flash memory organization
Page 1 Page 0
Page 2047
RE WE
Page 2047
EN
Page 1 Page 0
Addr[7:0] = Row[7:0]
4.2
Flash Memory
93
Typical Flash memory architecture, much like the other memory structures, consists of a memory core, address decoder, sense amplifier, data buffer and control logic as shown in Fig. 4.18. When a memory operation starts, the control logic enables the address decoder, the address register, and the appropriate data buffers in order to activate the read or the write data-path. The address in the address register is decoded to point the location of data in the memory core. If a read operation needs to be performed, the retrieved data is first stored in the data buffer, and then released to the bus. If the operation calls for a write, the data is stored in the data buffer first, and then directed to the designated address in the memory core. The standby mode neither writes to the memory nor reads from it. The hibernation mode disables the address decoder, memory core and data buffer to reduce power dissipation. The main Flash operation modes are tabulated in Fig. 4.19. I/O[7:0]
Data Buffer
EN RE
Control Logic
WE Reset
Address Register
Address[23:0]
Address Decoder
Sector 31
High voltage
Fig. 4.18 Generic Flash memory architecture
EN
RE
WE
reset
MODE
0
0
1
1
Read
0
1
0
1
Write (Program)
0
1
1
1
Standby
1
X
X
1
Hibernate
X
X
X
0
Hardware reset
Fig. 4.19 Main modes of a Flash memory
Sector 30
Sector 0
94
4
External Memories
As indicated above, the Flash memory cell in Fig. 4.16 is the basic storage element of the Flash memory core. It is an N-channel MOS transistor with a floating gate whose sole purpose is to store electronic charge. The device needs high voltages well above the power supply voltage to create and transfer electrons back and forth to the floating gate according to the need. In order to obtain a much higher DC voltage from power supply for a short duration, the control logic in Fig. 4.18 contains a charge pump circuit composed of a constant current source and a capacitor. As the constant current charges the capacitor, the voltage across the capacitor rises linearly with time, ultimately reaching a high DC potential to create channel electrons to migrate to the floating gate. The mechanism of electron tunneling to the floating gate requires time. Therefore, a write or erase operation may take many consecutive clock cycles compared to simple control operations such as suspend or resume. Figure 4.20 shows the basic read operation provided that data has already been transferred from the memory core to the data buffer. Once a valid address is issued, data is produced at the I/O terminal some time after the falling edge of the Read Enable signal, RE. Data is held at the I/O port for a period of hold time, th, following the rising edge of RE as shown in the timing diagram below. The actual read operation takes about four clock cycles as the entire data retrieval process from the memory core takes time. This involves sensing the voltage level at the Flash cell, amplifying this value using the sense amplifier, and propagating the data from the sense amplifier to the data buffer. Fig. 4.20 Basic read operation timing diagram
tcommand Address
Addr
EN
RE
WE
Reset th
tacc I/O[7:0]
Data
In contrast to read, the basic write operation follows the timing diagram of Fig. 4.21. In this figure, a valid address must be present at the address port when the Enable and Write Enable signals, EN and WE, are both at logic 0. Valid data satisfying the setup and hold times, ts and th, is subsequently written to the data buffer. The actual write process can take up to four clock cycles due to the data propagation from the I/O port to the data buffer, and then from the data buffer to the Flash cell.
4.2
Flash Memory
Fig. 4.21 Basic write (program) operation timing diagram
95 tcommand Addr
Address
EN
RE
WE
Reset th
ts I/O[7:0]
Data
Disabling the I/O port for read or write, and putting the device in hibernate mode requires EN signal to be at logic 1 as shown in Fig. 4.19. The I/O port will float and show high impedance (Hi-Z). For the demonstration, we use a 16Mbit serial Flash memory from ST Microelectronics, M25P16, housed on the Digilent Pmod-SF [4]. This Flash memory has four ports, the active-low slave select port, S (equivalent to SS ), clock input, C (equivalent to SCK), serial data input, D (equivalent to SDI), and serial data output, Q (equivalent to SDO). The basic instruction set for this memory consists of 11 instructions as shown in Fig. 4.22. The Write Enable instruction, 0x06, enables the Flash memory for program and erase operations. Otherwise, any attempt to write data or to erase data becomes impossible. The Write Disable instruction, 0x04, reverses the effect of Write Enable and erases the Write Enable bit so that program and erase cycles become ineffective. Device Identification instruction, 0x9F, identifies the manufacturer and the device with eight and 16-bit fields, respectively. The Read Data instruction, 0x03, allows reading data bytes from the Flash memory. The length of data ranges one to infinity. The Fast Read Data instruction, 0x0B, again reads bytes from a designated Flash address, but in a higher speed. Program (Write) Page instruction, 0x02, writes data bytes to a specified page address. The number of bytes range between one and 256 (the entire page). The Sector Erase instruction, 0xD8, erases the whole sector made out of 2048 pages. The Bulk Erase instruction, 0xC7, erases the entire chip. The Read Status Register instruction, 0x05, reads the contents of the status register to find out the status of a read, write or erase process. The status register can also be programmed by the Write Status Register instruction, 0x01 when Write Enable is activated or a certain area of the chip needs to be protected against further programming or erasure.
96
4
External Memories
INSTRUCTION SET 0000 0110 = 0x06 = Write Enable 0000 0100 = 0x04 = Write Disable 1001 1111 = 0x9F = Device Identification (1 to 3 bytes) 0000 0011 = 0x03 = Read Data (1 to ∞ bytes) 0000 1011 = 0x0B = Fast Read Data (1 to ∞ bytes) 0000 0010 = 0x02 = Write (Program) Page (1 to 256 bytes) 1101 1000 = 0xD8 = Sector Erase 1100 0111 = 0xC7 = Bulk Erase 0000 0101 = 0x05 = Read Status Register (1 to ∞ bytes) 0000 0001 = 0x01 = Write Status Register (1 byte) 1011 1001 = 0xB9 = Power-Down
Fig. 4.22 ST Microelectronics M25P16 Flash memory instruction set
A basic read sequence is shown in Fig. 4.23. The Read Data instruction, 0x03, is followed by a starting 24-bit address field, Address[23:0], from which data is read. Once the read command and valid address bits are introduced, the serial data becomes available from the data output port, Q, as long as the active-low slave select pin, S, is at logic 0. The read command, address bits and data are all produced at the negative edge of the clock, C. S C D
0
0
0
0
0
0
1
1
23
22
Q
0 7
Read Command = 0x03
Address[23:0]
6
5
4
3
Data1[7:0]
2
1
0
7
6
Data2[7:0]
Fig. 4.23 ST Microelectronics M25P16 Flash memory read sequence
The basic page write sequence starts with the Page Program command, 0x02, followed by a 24-bit address, Address[23:0], and serial data bytes as shown in Fig. 4.24. In this case, the write command, address bits and data are again produced at the negative edge of C.
4.2
Flash Memory
97
S C D
0
0
0
0
0
0
1
0
23
22
0
7
6
5
4
3
2
0
1
7
6
Q
Page Program Command = 0x02
Address[23:0]
Data2[7:0]
Data1[7:0]
Fig. 4.24 ST Microelectronics M25P16 Flash memory page program (write) sequence
The sector erase instruction consists of the Sector Erase command, 0xD8, followed by the sector address as shown in Fig. 4.25. This instruction erases the data at the designated sector address as long as the Write Enable bit is active and the sector is not protected. S C 1
D
1
0
1
1
0
0
0
23
22
0
Q
Sector Erase Command = 0xD8
Sector Address[22:0]
Fig. 4.25 ST Microelectronics M25P16 Flash memory sector erase
The Bulk Erase instruction, 0xC7, is only comprised of the bulk erase command, 0xC7, as shown in Fig. 4.26. With this instruction the contents of the entire Flash memory are erased if Write Enable bit is active and no protection is asserted. S C D
1
1
0
0
0
1
1
Q
Bulk Erase Command = 0xC7 Fig. 4.26 ST Microelectronics M25P16 Flash memory bulk erase
1
98
4
External Memories
The status register shown in Fig. 4.27 contains four entries. The Write Protect bit, WP, protects the Flash memory from writes or erases if it is at logic 1. On the contrary, the Write Enable Latch allows the Flash memory for writes and erases if it is at logic 1. The Write-In-Progress bit, WIP, shows there is any ongoing write process. The Block Protect bits, BP[2:0], can be employed to protect between no sectors and all sectors according to the truth table in Fig. 4.27. 7
6
5
WP
0
0
4
3
2
1
BP2 BP1 BP0 WEL
0 WIP
Block Protect[2:0]
WP = Write Protect WEL = Write Enable Latch WIP = Write In Progress Block Protect[2:0] entry in STATUS register 000 = No area is protected 001 = Sector 31 is protected 010 = Sectors 30, 31 are protected 011 = Sectors 28 to 31 are protected 100 = Sectors 24 to 31 are protected 101 = Sectors 16 to 31 are protected 110 = Sectors 0 to 31 are protected 111 = Sectors 0 to 31 are protected
Fig. 4.27 ST Microelectronics M25P16 Flash memory status register
Program 4.2 below writes one byte of data at a designated Flash memory address, and then reads from it. Because of its length, the program is divided into two sections as shown below. The first section shows the oscillator configuration for the bus master, and the primary and secondary function calls used during operation. The second section shows the master SPI configuration, chip I/O configuration and the operational details of writing a byte and reading the same byte.
4.2
Flash Memory
99
Program 4.2 // MCU programs the Digilent serial flash with 1 byte using SPI #include "xc.h" // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3
// primary oscillators are disabled
// Configuration Register - FOSCSEL #pragma config IESO = 0 #pragma config FNOSC = 7
// start with a user-selected oscillator at reset // select FRC oscillator with postscalar at reset
// Configuration Register - FICD #pragma config JTAGEN = 0 //JTAG is disabled #define PPSUnLock
__builtin_write_OSCCONL(OSCCON & 0xBF)
#define PPSLock
__builtin_write_OSCCONL(OSCCON | 0x40)
unsigned char SPI_Transmit (unsigned char TxValue) { while (SPI1STATbits.SPITBF == 1); SPI1BUF = TxValue;
// Wait until the TX buffer is empty due to a prior process // When empty, send the byte to the TX buffer
while (SPI1STATbits.SPIRBF == 0);
// As valid bits shifts out of SDO, junk bits are received from SDI
return SPI1BUF;
// Wait until the RX buffer is full of junk data // When full, read the junk data in RX buffer through SPI1BUF
} unsigned char SPI_Receive () { while(SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = 0x00; // When empty, send the junk byte (0x00) to the TX buffer while(SPI1STATbits.SPIRBF == 0); // As junk bits shifts out of SDO, valid bits are received from SDI // Wait until the RX buffer is full of valid data // When full, read the valid data in RX buffer through SPI1BUF
return SPI1BUF; } void WREN () { LATBbits.LATB6 = 0;
// Lower SS for instruction delivery
SPI_Transmit (0x06); LATBbits.LATB6 = 1;
// Send the WREN instruction // Raise SS for instruction completion
} unsigned char Read_Status_Reg () { LATBbits.LATB6 = 0; SPI_Transmit (0x05);
// Lower SS for instruction delivery // Send the Read status register instruction
100
4
External Memories
unsigned char status = SPI_Receive (); // Read the status register LATBbits.LATB6 = 1; // Raise SS for instruction completion return status; }
unsigned char Write_Byte (unsigned char command, long int address, unsigned char SentByte) { LATBbits.LATB6 = 0;
// Lower SS to start PP
SPI_Transmit (command); // Send the PP instruction SPI_Transmit ((address >> 16) & 0xFF); // Send the MS byte of the 24-bit address SPI_Transmit ((address >> 8) & 0xFF); // Send the mid byte of the 24-bit address SPI_Transmit (address & 0xFF); // Send the LS byte of the 24-bit address SPI_Transmit (SentByte); unsigned char JunkByte = SPI_Receive (); LATBbits.LATB6 = 1; // Raise SS for instruction completion return JunkByte; } unsigned char Read_Byte (unsigned char command, long int address) { LATBbits.LATB6 = 0; SPI_Transmit (command);
// Lower SS to start PP // Send the PP instruction
SPI_Transmit ((address >> 16) & 0xFF); // Send the MS byte of the 24-bit address SPI_Transmit ((address >> 8) & 0xFF); // Send the mid byte of the 24-bit address SPI_Transmit (address & 0xFF); // Send the LS byte of the 24-bit address unsigned char ReceivedByte = SPI_Receive (); LATBbits.LATB6 = 1;
// Raise SS for instruction completion
return ReceivedByte; } void main() { // Clock source definition - A single FRC internal clock // OSCTUN Register OSCTUNbits.TUN = 0;
// select FRC = 7.37MHz
// CLKDIV Register CLKDIVbits.ROI = 0;
// interrupts have no effect on clock recovery
CLKDIVbits.FRCDIV = 3;
// FOSC = FRC/8 = 7.37MHz/8 and FP = FOSC/2 = 460KHz
// SPI configuration MASTER mode sending 8 bits SPI1CON1bits.DISSCK = 0; // Enable the internal SPI clock SPI1CON1bits.DISSDO = 0; // Enable the SPI data output, SDO SPI1CON1bits.MODE16 = 0; // Enable the 8-bit data mode SPI1CON1bits.SSEN = 0; // This unit is not a slave so Slave Select pin is not used SPI1CON1bits.MSTEN = 1;
// Enable MASTER mode
4.2
Flash Memory
101
SPI1CON1bits.SMP = 0;
// Sample data at the pos edge when received at the neg edge of SCK
SPI1CON1bits.CKE = 1; SPI1CON1bits.CKP = 0;
// Output data changes when SCK goes from ACTIVE to IDLE state // SCK polarity: IDLE is low phase and ACTIVE is high phase of SCK
SPI1CON1bits.PPRE = 1;
// Primary SPI clock pre-scale is 1:1
SPI1CON1bits.SPRE = 7;
// Secondary SPI clock pre-scale is 1:1 -> SCK = 460KHz
SPI1STATbits.SPIROV = 0; SPI1STATbits.SPIEN = 1;
// Clear initial overflow bit in case an overflow condition in SPI1BUF // Enable the SPI interface
// Peripheral Pin Select with RP pins PPSUnLock; RPOR4bits.RP8R = 8;
// RP8 is an output for SCK1
RPINR20bits.SCK1R = 8;
// RP8 is an input for SCK1
RPOR4bits.RP9R = 7; RPINR20bits.SDI1R = 7;
// RP9 is an output for SDO1 // RP7 is an input for SDI1
RPOR3bits.RP6R = 9; PPSLock;
// RP6 is an output for SS1
// Define I/O TRISBbits.TRISB6 = 0;
// Configure RB6 as an output for SS
TRISBbits.TRISB8 = 0; TRISBbits.TRISB9 = 0;
// Configure RB8 as an output for SCK1 // Configure RB9 as an output SDO1
TRISBbits.TRISB7 = 1;
// Configure RB7 as an input for SDI1
// Write 1 byte and then read it WREN(); while ((Read_Status_Reg () & 0x02) == 0x00); // While WEL = 0 keep reading the status register Read_Status_Reg (); Write_Byte (0x02, 0x020000, 0xAA);
// When WEL = 1, perform byte write
while ((Read_Status_Reg () & 0x01) == 0x01); // While WIP = 1 keep reading the status register Read_Status_Reg (); Read_Byte (0x03, 0x020000);
// When WIP = 0 read the byte back
}
Configuring the oscillator on dspic33fj128mc802 and defining the oscillation frequency is shown in Fig. 4.28. The pragma statements configure the type of oscillator to be used for this chip. Since we use a single internal FRC clock, we disable clock switch-over mechanism. Therefore, we program the clock mode and switch-over statements as #pragma config FCKSM = 3 and #pragma config IESO = 0. Since only an internal clock is used, the statement, #pragma config OSCIOFNC = 0, removes the OSCO pin from being connected to external crystal. Finally, the statement, #pragma config FNOSC = 7, selects the internal FRC oscillator. The OSCTUN and CLKDIV registers define the clock frequency to be 7.37MHz. However, a post-scalar clock divider divides the initial clock at 7.37 MHz, and produces 921 KHz at the FOSC and 460 KHz at the FP outputs.
102
4
External Memories
// MCU programs the Digilent serial flash with 1 byte using SPI #include "xc.h" // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled // Configuration Register - FOSCSEL #pragma config IESO = 0 // start with a user-selected oscillator at reset #pragma config FNOSC = 7 // select FRC oscillator with postscalar at reset ... void main() { // Clock source definition - A single FRC internal clock // OSCTUN Register OSCTUNbits.TUN = 0; // select FRC = 7.37MHz // CLKDIV Register CLKDIVbits.ROI = 0; CLKDIVbits.FRCDIV = 3;
// interrupts have no effect on clock recovery // FOSC = FRC/8 = 7.37MHz/8 and FP = FOSC/2 = 460KHz
... }
Fig. 4.28 Configuration of the oscillator for SPI interface
Figure 4.29 shows the SPI I/O port configuration for the bus master. In this figure, RP6 is configured as an output pin by TRISBbits.TRISB6 = 0, and assigned to the slave select pin, SS1, by the statement, ROPR3bits.RP6R = 9. RP8 is also configured as an output pin by TRISBbits.TRISB8 = 0, and required to operate both as an output and an input by the ROPR4bits.RP8R = 8 and RPINR20bits.SCK1R = 8 statements, respectively. RP9 is configured as an output by the statement, TRISBbits.TRISB9 = 0, and assigned to SDO1 by the statement, ROPR4bits. RP9R = 7. Lastly, RP7 is configured as an input by the TRISBbits.TRISB7 = 1 statement, and assigned to SDI1 by the RPINR20bits.SDI1R = 7 statement.
4.2
Flash Memory
103
// MCU programs the Digilent serial flash with 1 byte using SPI #include "xc.h" ... #define PPSUnLock #define PPSLock
__builtin_write_OSCCONL(OSCCON & 0xBF) __builtin_write_OSCCONL(OSCCON | 0x40)
void main() { … // Peripheral Pin Select with RP pins PPSUnLock; RPOR4bits.RP8R = 8; // RP8 is an output for SCK1 RPINR20bits.SCK1R = 8; // RP8 is an input for SCK1 RPOR4bits.RP9R = 7; // RP9 is an output for SDO1 RPINR20bits.SDI1R = 7; // RP7 is an input for SDI1 RPOR3bits.RP6R = 9; // RP6 is an output for SS1 PPSLock; // Define I/O TRISBbits.TRISB6 = 0; TRISBbits.TRISB8 = 0; TRISBbits.TRISB9 = 0; TRISBbits.TRISB7 = 1;
// Configure RB6 as an output for SS // Configure RB8 as an output for SCK1 // Configure RB9 as an output SDO1 // Configure RB7 as an input for SDI1
... }
Fig. 4.29 Definition of the chip I/O for reading and writing data packets
Configuration of SPI interface is shown in Fig. 4.30. This figure is identical to the one in Fig. 4.12 where the SPI1CONbits.DISSCK = 0 and SPI1CONbits. DISSDO = 0 statements enable the SCK1 and SDO1 outputs, respectively. The SPI1CONbits.MODE16 = 0 statement indicates byte-wide data packets. The SPI1CONbits.MSTEN = 1 and SPI1CONbits.SSEN = 0 statements enable the SPI1 interface as the master mode and removes the SS pin to be used as an input. The SPI1CONbits.SMP = 0 statement samples the data at the positive edge of SCK1 when the data is delivered at the negative edge. The SPI1CONbits.CKE = 1 statement forces the master to change data when SCK1 goes from active to idle state, and
104
4
External Memories
the SPI1CONbits.CKP = 0 statement defines the idle state as the low phase, and the active state as the high phase of SCK1. The combination of these two instructions produces data at the negative edge of SCK1. The statements, SPI1CONbits.PPRE = 1 and SPI1CONbits.SPRE = 7, make any division of SCK1 irrelevant, and forces SCK1 to oscillate at 460KHz at the FP terminal. The statement, SPI1STATbits. SPIROV = 0, clears the overflow bit in the SPI1BUF register, and prepares the SPI1 unit to receive and process new data immediately. The statement, SPI1STATbits. SPIEN = 1, enables the SPI1 master interface. // MCU programs the Digilent serial flash with 1 byte using SPI ... void main() { … // SPI configuration MASTER mode sending 8 bits SPI1CON1bits.DISSCK = 0; // Enable the internal SPI clock SPI1CON1bits.DISSDO = 0; // Enable the SPI data output, SDO SPI1CON1bits.MODE16 = 0; // Enable the 8-bit data mode SPI1CON1bits.SSEN = 0; // This unit is not a slave so Slave Select pin is not used SPI1CON1bits.MSTEN = 1; // Enable MASTER mode SPI1CON1bits.SMP = 0; // Sample input in the middle of data output period SPI1CON1bits.CKE = 1; // SCK edge: Output data changes when SCK goes from ACTIVE to IDLE state SPI1CON1bits.CKP = 0; // SCK polarity: IDLE is low phase and ACTIVE is high phase of SCK SPI1CON1bits.PPRE = 1; // Primary SPI clock pre-scale is 1:1 SPI1CON1bits.SPRE = 7; // Secondary SPI clock pre-scale is 1:1 -> SCK = 460KHz SPI1STATbits.SPIROV = 0; // Clear initial overflow bit in case an overflow condition in SPI1BUF SPI1STATbits.SPIEN = 1; // Enable the SPI interface … }
Fig. 4.30 SPI configuration for reading and writing data packets
Figure 4.31 is identical to Fig. 4.13 in terms of describing the primary function calls, SPI_Receive and SPI_Transmit. These two functions show how the SPI master interface receives and transmits serial data using the SPIRBF and SPITBF bits of the SPI1 status register and SPI1BUF register in detail.
4.2
Flash Memory
105
// MCU programs the Digilent serial flash with 1 byte using SPI #include "xc.h" … unsigned char SPI_Transmit (unsigned char TxValue) { while (SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = TxValue; // When empty, send the byte to the TX buffer while (SPI1STATbits.SPIRBF == 0); // As valid bits shifts out of the SDO port, junk bits are received from the SDI port // Wait until the RX buffer is full of junk data // When full, read the junk data in RX buffer through SPI1BUF return SPI1BUF; } unsigned char SPI_Receive () { while(SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = 0x00; // When empty, send the junk byte (0x00) to the TX buffer while(SPI1STATbits.SPIRBF == 0); // As junk bits shifts out of the SDO port, valid bits are received from the SDI port // Wait until the RX buffer is full of valid data return SPI1BUF; // When full, read the valid data in RX buffer through SPI1BUF } ... void main() { … }
Fig. 4.31 Transmit and receive function calls for M25P16 Flash memory
Figure 4.32 shows the second set of functions to read and write serial data to the Flash memory. All four functions in this category are based on the primary function calls, SPI_Transmit and SPI_Receive. Each function is executed by the active-low SS pin, RP6, at logic 1, and by the statements, LATBbits.LATB6 = 0 and LATBbits.LATB6 = 1, to start and stop the data transfer, respectively. The first function, WREN, enables the Flash memory to write and erase data. After lowering RP6 to logic 0 by LATBbits.LATB6 = 0, the function, SPI_Transmit (0x06), sends the write enable command to set the Write Enable Latch bit, WEL, in the status register. The function call ends with the LATBbits.LATB6 = 1 statement, which raises RP6 to logic 1, and deselects the Flash memory. The second function, Read_Status_Reg, reads the contents of the Flash memory’s status register. The read command, 0x05, is sent to the SRAM module by SPI_Transmit (0x05). This is followed by the SPI_Receive function which reads the contents of the status register to a temporary register called “status”. When the read process is complete, RP6 is raised to logic 1, and the contents of the temporary register, status, are read by the statement, return status.
106
4
External Memories
The third function, Write_Byte, describes the actual write process to the Flash memory. The Write_Byte function is comprised of three sub function calls, each of which is based on the SPI_Transmit primary function. The first sub function, SPI_Transmit (command), sends the Page Program command, 0x02, to the Flash memory’s SPI interface. The three statements following the command statement assemble a 24-bit Flash memory address, and sent to the Flash memory interface one after the other. The statement, SPI_Transmit ((Address >> 16) & 0xFF), shifts the 24-bit Flash memory address to the right by 16 bits to form the most significant byte while the other two statements, SPI_Transmit ((Address >> 8) & 0xFF) and SPI_Transmit (Address & 0xFF), form the mid and least significant address bytes, respectively. The write process ends with the SPI_Transmit (SentByte) statement where a user data byte, SentByte, is sent to the Flash memory. However, while transmitting data, the serial data received by the SPI interface also needs to be handled. This is accomplished by the SPI_Receive function which stores the received data into a temporary register, JunkByte. The contents of this register are subsequently read by the statement, return JunkByte, and disposed. The fourth function, Read_Byte, defines the read process from the Flash memory. The read command, 0x03, is sent by the SPI_Transmit (0x03) statement. This is followed by three statements, SPI_Transmit ((Address >> 16) & 0xFF), SPI_Transmit ((Address >> 8) & 0xFF) and SPI_Transmit (Address & 0xFF), to form a 24-bit Flash memory address. The SPI_Receive function following the address statements delivers a byte of data from a designated Flash memory address and stores it in an auxiliary register, ReceivedByte. The contents of the auxiliary register are consequently read by the statement, return ReceivedByte, which terminates the read process.
4.2
Flash Memory
107
// MCU programs the Digilent serial flash with 1 byte using SPI #include "xc.h" … void WREN () { LATBbits.LATB6 = 0; SPI_Transmit (0x06); LATBbits.LATB6 = 1; }
// Lower SS for instruction delivery // Send the WREN instruction // Raise SS for instruction completion
unsigned char Read_Status_Reg () { LATBbits.LATB6 = 0; SPI_Transmit (0x05); unsigned char status = SPI_Receive (); LATBbits.LATB6 = 1; return status; }
// Lower SS for instruction delivery // Send the Read status register instruction // Read the status register // Raise SS for instruction completion
unsigned char Write_Byte (unsigned char command, long int address, unsigned char SentByte) { LATBbits.LATB6 = 0; // Lower SS to start PP SPI_Transmit (command); // Send the PP instruction SPI_Transmit ((address >> 16) & 0xFF); // Send the MS byte of the 24-bit address SPI_Transmit ((address >> 8) & 0xFF); // Send the mid byte of the 24-bit address SPI_Transmit (address & 0xFF); // Send the LS byte of the 24-bit address SPI_Transmit (SentByte); unsigned char JunkByte = SPI_Receive (); LATBbits.LATB6 = 1; // Raise SS for instruction completion return JunkByte; } unsigned char Read_Byte (unsigned char command, long int address) { LATBbits.LATB6 = 0; // Lower SS to start PP SPI_Transmit (command); // Send the PP instruction SPI_Transmit ((address >> 16) & 0xFF); // Send the MS byte of the 24-bit address SPI_Transmit ((address >> 8) & 0xFF); // Send the mid byte of the 24-bit address SPI_Transmit (address & 0xFF); // Send the LS byte of the 24-bit address unsigned char ReceivedByte = SPI_Receive (); LATBbits.LATB6 = 1; // Raise SS for instruction completion return ReceivedByte; } void main() { … }
Fig. 4.32 Major function calls for M25P16 Flash memory
108
4
External Memories
The application program that writes a byte to an arbitrary Flash memory address, and reads it back is shown in Fig. 4.33. The first statement in this application program is to activate the WEL bit to be able to write data to the Flash memory core. This is accomplished by the function call, WREN, which writes the Write Enable command, 0x06, in order to set the WEL bit in the status register. The second statement is a while loop, while ((Read_Status_Reg () & 0x02) == 0x00), which reads the contents of the status register and isolates the WEL bit. The application program keeps reading the contents of the status register as long as WEL = 0. When WEL = 1, due to the previous WREN function, the Page Program command, 0x02, a 24-bit starting Page Address, 0x020000, and an arbitrary byte of data, 0xAA, are serially sent to the Flash memory by the function, Write_Byte (0x02, 0x020000, 0xAA). The second while loop, while ((Read_Status_Reg () & 0x01) == 0x01), isolates the WIP bit in the status register, and repeatedly reads the contents of the status register until this bit becomes logic 0. The application program finally reads the contents of the Flash memory where the previous write took place by issuing the Read Command, 0x03, followed by the previous address, 0x020000, in order to retrieve the byte, 0xAA.
// MCU programs the Digilent serial flash with 1 byte using SPI #include "xc.h" … void main() { … // Write 1 byte and then read it WREN(); while ((Read_Status_Reg () & 0x02) == 0x00); // While WEL = 0 keep reading the status register Read_Status_Reg (); Write_Byte (0x02, 0x020000, 0xAA); // When WEL = 1, perform byte write while ((Read_Status_Reg () & 0x01) == 0x01); // While WIP = 1 keep reading the status register Read_Status_Reg (); Read_Byte (0x03, 0x020000); // When WIP = 0 read the byte back }
Fig. 4.33 M25P16 Flash memory operation using SPI
References
109
Projects 1. Write eight bytes of data to a 64KB Microchip 23K640 SRAM starting from the address 0x0000 as shown in Program 4.1. Once the transaction is complete, read the data block back for verification. 2. Write eight bytes of data to a 16MBit ST Microelectronics M25P16 Flash memory starting from the address 0x020000. Once the transaction is complete, read the entire data array for verification. 3. Now, write a program to impose write protection to all sectors of the Flash memory. Write eight bytes of data starting from the address 0x020000. Read back the data array from the Flash memory. What values do you read? 4. Write eight bytes of data to the Flash memory starting from the address 0x020000. Next, impose write protection to all sectors of the Flash memory. Write a second set of eight bytes starting from the same address. Read back the data array from the Flash memory. Was the write protection set up successful? What values do you read?
References 1. dspic33fj128mc802/804 Microchip processor datasheet, https://www.microchip.com/en-us/ product/dsPIC33FJ128MC802, Accessed January 2023. 2. Microchip 23K640 SRAM module datasheet, https://www.microchip.com/en-us/product/23 K640, Accessed January 2023. 3. MPLABX software design environment, https://www.microchip.com/en-us/tools-resources/ develop/mplab-x-ide, Accessed January 2023. 4. ST Microelectronics M25P16 Flash memory datasheet, https://datasheet.octopart.com/M25P16VME6G-STMicroelectronics-datasheet-7623188.pdf, Accessed January 2023.
Chapter 5
Data Converters
The microcontroller often has to deal with analog signals because of the nature of sensors. These signals are often very small in the range of microvolts or microamps depending on the sensor, and contain noise or offset embedded in their output signals. In order to obtain a “healthy” signal, the original signal from a sensor is “cleaned” from unwanted components, and then amplified by signal conditioning circuits. The reconditioned signal is then directed to the input of an Analog-toDigital Converter (ADC) where the digital equivalent of the amplified analog signal is produced. Once there, the digital signal is processed by the CPU according to the embedded program that resides in the program memory. At the opposite end of this spectrum, digital signals often need to be converted into an analog form to drive an output device, such as a DC motor. In this case, a digital signal is produced at the parallel port of a microcontroller as a result of an embedded application program in the program memory. The digital output is then converted into an analog signal by a Digital-to-Analog Converter (DAC) to be applied to an analog device. Figure 5.1 illustrates simplified data-paths for ADC and DAC interfacing a microcontroller.
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 A. Bindal, Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers, https://doi.org/10.1007/978-3-031-27841-9_5
111
112
5 Digital Signal
Small Analog Signal
Sensor
Differential Amp
ADC
Cleaned and Amplified Analog Signal
Micro Controller
Data Converters
Digital Signal
Analog Output Device
DAC
Analog Signal
Fig. 5.1 Simplified data-paths for ADC and DAC
5.1
Introduction to Analog-to-Digital Converter (ADC)
The signal resolution is an important factor to consider in an ADC. It simply means dividing a sampled analog signal by 2N number of voltage levels to represent its value by N number of bits at the ADC output. The second important consideration is the range of analog signals from a minimum to a maximum value that an ADC can receive and process. Figure 5.2 describes the ADC resolution in a numerical example where analog signal changes between 0 V and 5 V. The bit resolution is only three bits. Therefore, the ADC divides the maximum value of the analog signal by 23 = 8 levels, and identifies each analog level with three output bits. For example, an analog signal of 2.501 V in this figure is identified by a digital output of 100. If the analog signal increases to 3.124 V, the digital output that represents this voltage value still stays at 100. In other words, in a three-bit ADC there is no difference between 2.501 V and 3.124 V in terms of their digital representation. The 0.625 V step size is the natural occurring error in a three-bit ADC, and it can be reduced only if the number of bits in the ADC is increased. In general, increasing the number of ADC bits by one halves the error. Therefore, having a four-bit ADC instead of a three-bit reduces the quantization error by 0.312 V. Reference voltage is the maximum voltage level of the analog signal, and it is used to calculate the step size. In this three-bit ADC example, the reference voltage is 5 V because the amplified analog voltage at the input of the ADC is limited not go beyond 5 V.
5.1
Introduction to Analog-to-Digital Converter (ADC)
113
Reference Voltage – Minimum Voltage
5-0
Step Size =
= 2ADC Bit Size
= 0.625V 23
Maximum Digital Output 111
Digital Output
110 101 100 23 Levels 011 010 001
5.000
4.375
3.750
3.125
2.500
1.875
1.250
0.625
0.000
000
Analog Signal (V) Reference Voltage
Fig. 5.2 Input-output description of a three-bit ADC
The ADC samples non-periodic analog signals in regular time intervals as described in Fig. 5.3. The time interval between sampling points is called the sampling period. The sampling period is adjusted according to the conversion speed of the ADC in order to generate accurate digital outputs. 5.000 4.375
Sampling Point
Signal (V)
3.750 3.125 2.500 1.875 1.250 0.625 0.000 Sampling Period
Fig. 5.3 Sampling of a continuous analog signal
Time (sec)
114
5
Data Converters
Once sampled, the analog voltage at the input of ADC is held constant throughout the conversion period as shown in Fig. 5.4. The shape of the converted signal may be quite different from the original analog signal due to the ADC resolution and the time duration between samples. In a three-bit ADC, sampling takes place in 0.625 V increments. Therefore, each sampling point becomes subject to a dynamic quantization error which changes between 0 V and 0.312 V. For example, a three-bit ADC samples 3.4 V according to its closest sampling level of 3.125 V, and produces a 0.275 V error. Arbitrary signals that change faster than the sampling frequency are subject to much larger errors. When converted back to their analog form, these signals show large deviations from their original shapes as shown in Fig. 5.4. A basic sample-and-hold circuit consists of an NMOS transistor and a capacitor 5.000 Quantized Signal 4.375
Sampling Point
3.750 Quantization Error
Signal (V)
3.125 2.500
Step Size
1.875 1.250 0.625 0.000 Sampling Period
Time (sec)
Fig. 5.4 Sampling period, hold concept and regeneration of an analog signal
as shown in Fig. 5.5. The control input uses an N-channel MOSFET as a switch and turns it on for a short period of time, called sampling width, to store the analog voltage level at a capacitor. When the transistor is turned off, this analog value is held constant until the analog-to-digital conversion is complete.
5.2
Flash ADC
115 Quantized Signal
Analog Input
Capacitor Control Input Sampling Width
Fig. 5.5 Simplified sample-and-hold circuit
There are three types of Analog-to-Digital converters: flash-type, ramp-type and successive approximation-type. There are also other hybrid forms that originate from the three primary types.
5.2
Flash ADC
The simplest ADC is the flash-type as shown in Fig. 5.6. This three-bit ADC contains 23 = 8 operational amplifiers. The analog signal is applied to all eight positive input terminals. The reference voltage is distributed to each negative input terminal via a voltage divider circuit. Each operational amplifier acts as a differential amplifier, and amplifies the difference between a continuously changing analog signal and the portion of the reference voltage. According to this figure, when the analog voltage is less than or equal to 0.625 V, only Out[0] becomes logic 1; all other outputs from Out[1] to Out[7] become logic 0. When the analog signal exceeds 0.625 V but less than 1.25 V, only Out[0] and Out [1] become logic 1; again all other outputs become logic 0. Higher analog voltages successively produce more logic 1 levels in this schematic. An encoder is placed at the output of all eight operational amplifiers to convert the voltage levels at Out[7:0] into a three-bit digital output, DOut[2:0] . Table 5.1 describes this conversion for the three-bit flash ADC in Fig. 5.6.
116
5
Analog Input
Data Converters
Out[7] R
Reference Voltage
4.375V
R Out[6] 3.750V
R Out[5] DOut[2]
R Out[4] 2.500V
R Out[3] 1.875V
FLASH ADC ENCODER
3.125V
DOut[1]
R Out[2] DOut[0] 1.250V
R Out[1] 0.625V
R Out[0] 0.0V
Fig. 5.6 A typical three-bit flash ADC schematic
5.3
Ramp ADC
117
Table 5.1 Three-bit flash ADC truth table describing its operation Analog Input
Out[7] Out[6] Out[5] Out[4] Out[3] Out[2] Out[1] Out[0]
DOut[2] DOut[1] DOut[0]
0.625 > VIN > 0.000
0
0
0
0
0
0
0
1
0
0
0
1.250 > VIN > 0.625
0
0
0
0
0
0
1
1
0
0
1
1.875 > VIN > 1.250
0
0
0
0
0
1
1
1
0
1
0
2.500 > VIN > 1.875
0
0
0
0
1
1
1
1
0
1
1
3.125 > VIN > 2.500
0
0
0
1
1
1
1
1
1
0
0
3.750 > VIN > 3.125
0
0
1
1
1
1
1
1
1
0
1
4.375 > VIN > 3.750
0
1
1
1
1
1
1
1
1
1
0
5.000 > VIN > 4.375
1
1
1
1
1
1
1
1
1
1
1
5.3
Ramp ADC
The ramp ADC uses only a single operation amplifier, but it employs an up-counter and a Digital-to-Analog Converter (DAC) in a loop structure shown in Fig. 5.7. The digital output is obtained from C[2:0] terminals, and progressively forms within several clock periods as opposed to being almost instantaneous as in the flash ADC.
conversion clock
INC
reset
3-bit Counter C[2]
C[1]
+VCC = 5V DACOUT S/HOUT
S/H
-VCC = 0V
Fig. 5.7 A typical four-bit ramp ADC schematic
Analog Input
DAC
C[0]
118
5
Data Converters
The top portion of Table 5.2 describes the digital output as a function of analog voltage in a three-bit ramp ADC. Table 5.2 Three-bit ramp ADC truth table describing its operation Step Size = 5/23 = 0.625V Analog Value (V) 0.0000 0.6250 1.2500 1.8750 2.5000 3.1250 3.7500 4.3750
C[2] C[1] C[0] 0 0 0 0 1 1 1 1
0 0 1 1 0 0 1 1
0 1 0 1 0 1 0 1
Analog Input = 2V Step 1 2 3 4 5
C[2] C[1] C[0] 0 0 0 0 1
0 0 1 1 0
0 1 0 1 0
DACOUT(V)
INC(V)
0.000 0.625 1.250 1.875 2.500
5.0 5.0 5.0 5.0 0.0
Final output with quantization error of 0.625V
The lower part of the table shows an example to illustrate how the conversion takes place in this ADC. Prior to its operation, the three-bit up-counter is reset and produces C[2:0] = 000. Assuming an analog voltage of 2 V is applied to the input, which must be kept constant until the conversion is complete, C[2:0] = 000 forces the DAC output, DACOUT, to produce 0 V. Since this value is less than 2 V at the sample/hold circuit output, S/HOUT, the output of the differential amplifier, INC, transitions to the positive supply potential of the operational amplifier, +VCC = 5 V (or INC = logic 1), and prompts the three-bit counter to increment to C[2:0] = 001. Consequently, DACOUT becomes 0.625 V according to the truth table above. However, this value is still less than S/HOUT = 2 V. Therefore, the differential amplifier produces another INC = logic 1 which prompts the counter to increment again to C[2:0] = 010. Up-counting continues until C[2:0] = 100 or
5.4
Successive Approximation ADC
119
DACOUT = 2.500 V. Since this last voltage is greater than S/HOUT = 2 V, the differential amplifier output switches back to its negative supply voltage, -VCC = 0 V (or INC = logic 0), and stops the up-counter from incrementing further. The digital output stays steady at C[2:0] = 100 from this point forward, representing 2 V analog voltage with a dynamic error of 0.5 V.
5.4
Successive Approximation ADC
The third type of ADC is based on the successive approximation technique to estimate the value of the analog voltage. This type is a trade-off between the flashtype and the ramp-type in terms of speed and the number of components used in the circuit. As a numerical example, the steps of a successive approximation conversion are shown in Fig. 5.8 for a three-bit ADC.
Fig. 5.8 Steps of conversion in three-bit successive approximationtype ADC
First iteration
Second iteration
Third iteration
4.375
111
4.375
111
4.375
111
2.500
100
2.500
100
2.500
100
1.875
011
2.000V
0.000
000
1.250
010
1.250
010
0.000
000
0.000
000
After the successive approximation ADC resets, its output becomes 100. This value corresponds to an analog voltage of 2.5 V, and it is midway between 111 (for the maximum analog voltage of 4.375 V and above) and 000 (for the minimum analog voltage of 0 V).
120
5
Data Converters
Assume an analog voltage of 2 V is applied to the input of this ADC as in the previous examples. Then in the first iteration, the circuit compares the applied voltage against 2.5 V to see if it is greater than 2.5 V or not. After comparison the circuit decides that it is less than 2.5 V. This ends the first conversion period. Knowing that 2 V is below 2.5 V, in the second iteration the circuit ignores all voltages above 2.5V, and compares if 2 V is between 0 V and 1.25 V or between 1.25 V and 2.5 V. It decides that 2 V is between 1.25 V and 2.5 V. This ends the second conversion period. Again knowing that 2 V resides between 1.25 V and 2.5 V from the second iteration, this time the circuit compares if 2 V is between 1.25 V and 1.875 V or between 1.875 V and 2.5 V. It decides that 2 V is between 1.875 V and 2.5 V, which ends the third conversion period. The ADC produces 011, corresponding to 2 V. However, 011 level does in fact correspond to 1.875 V according to Fig. 5.8. Therefore, the successive approximation ADC produces a result with a 0.125 V error.
5.5
Analog-to-Digital Converter in dspic33fj128mc802
The ADC that resides in dspic33fj128mc802, ADC1, has four channels, CH0 to CH3, both in 10-bit and 12-bit resolutions [1]. A closer look at the ADC1’s data path reveals the following schematic in Fig. 5.9. In 10-bit mode, this ADC can sample four analog inputs at the same time, using all four channels. Any one of the six analog inputs, AN0 to AN5, can be applied to CH0; AN0 or AN3 are applied to CH1, AN1 or AN4 to CH2, and AN2 or AN5 to CH3. According to this scheme, while AN3 (or any other five analog inputs) is applied to CH0, the inputs, AN0, AN1 and AN2, can be applied to CH1, CH2 and CH3, respectively. In a similar fashion, while AN2 (or any other five analog inputs) is applied to CH0, AN3, AN4 and AN5 can be applied to CH1, CH2 and CH3, respectively. In 12-mode mode, only one analog input can be continuously sampled through CH0, and it can be any one of the inputs, AN0 to AN5. The conversion rate for a 10-bit ADC is 1100Ksamples/sec at four channels. However, this rate drops to 500Ksamples/sec for a single channel in 12-bit ADC resolution. The schematic in Fig. 5.9 also uses an external reference voltage, VREFL, which can be applied to any of the four channels instead of using the native reference voltage.
5.5
Analog-to-Digital Converter in dspic33fj128mc802 AN0 AN1
0 1
AN5
5
Channel Scan
121
S/H0 CH0SB[4:0]
CH0SA[4:0]
CSCNA AN1
1
VREF-
0
CH0NA
CH0NB
AN0
0
AN3
1
S/H1 CH123SA
VREF+
CH123SB
AVDD
VREF+
AVDD
VREFAN1
VCFG[2:0] 0 CH0 OUTPUT
AN4
1 CH1 OUTPUT S/H2
CH123SA
CH2 OUTPUT
Successive Approximation ADC
ADC1 BUF
CH123SB CH3 OUTPUT
VREFAN2
0
AN5
1
S/H3 CH123SA
CH123SB
VREF-
Fig. 5.9 10/12-bit ADC1 unit dspic33fj128mc802
The data-path in Fig. 5.9 allows simultaneous or sequential scanning of analog signals. The simultaneous scanning samples all four signals at the same time by four different Sample/Hold circuits, S/H0 to S/H3 as shown in Fig. 5.10. However, since there is only one successive approximation ADC, and the conversion takes place one after the other. That means the sampled signal at the S/H0 output is converted first from CH0. When done, the sampled signal at the S/H1 output is converted, using CH1. This trend follows until the S/H3 output is converted before all analog inputs are sampled again.
122
5
CH0 Sample 1
Convert 1
CH1 Sample 1
Sample 2
Convert 1
CH2 Sample 1
Convert 2
Sample 2
Convert 1
CH3 Sample 1
Convert 2
Sample 2
Convert 1
Data Converters
Convert 2
Sample 2
Convert 2
S/H and Conversion Time
S/H and Conversion Time
Fig. 5.10 Simultaneous sampling
Sequential scanning is a format that samples and then converts one analog signal before it samples and converts the next as shown in Fig. 5.11. According to this scheme, the analog input at CH0 is sampled and converted first. This follows sampling and converting the pending analog inputs at CH1, CH2 and CH3 before CH0 is sampled and converted again. CH0 Sample 1
CH1
CH2
CH3
Convert 1
Sample 1
Sample 2
Convert 1
Sample 1
Convert 2
Sample 2
Convert 1
Sample 1
S/H and Conversion Time
Convert 2
Sample 2
Convert 1
Convert 2
Sample 2
Convert 2
S/H and Conversion Time
Fig. 5.11 Sequential sampling
The analog input to ADC1 unit is selected by enabling two tri-state buffers in series for each analog input pin, from AN0 to AN5, as shown in Fig. 5.12. The hardware that enables these buffers originates from the ADC1 pin configuration register, AD1PCFGL, as we shall examine later in this chapter.
5.5
Analog-to-Digital Converter in dspic33fj128mc802
123 TRISA0 = 1
LAT REG
TO DATA BUS
PORT REG
AN0 PIN
TRISA0 = 1 To ADC input 0
PCFG0 = 0
TRISA1 = 1
LAT REG
TO DATA BUS
AN1 PIN
PORT REG
TRISA1 = 1 To ADC input 1
PCFG1 = 0
TRISA5 = 1
LAT REG
TO DATA BUS
PORT REG
AN5 PIN
TRISA5 = 1 To ADC input 5
PCFG5 = 0
Fig. 5.12 The input stage of ADC1
124
5
Data Converters
The ADC1 supports 10 bit or 12-bit output formats as mentioned earlier. Once the conversion cycle is complete, the 10-bit or 12-bit data at the ADC1 output is stored in the 16-bit ADC1BUF register. The 10-bit data format supports four different numbering systems as shown in Fig. 5.13. The ADC output format is defined by FORM[1:0] bits in the ADC1 control register, AD1CON1. The first number type is the signed fraction where fractions between +0.999 and -1.000 are represented in 10 bits in a 16-bit field according to the topmost row of Fig. 5.13. In this section, the sign bit becomes the complemented form of the most significant data bit, D8. The lower six bits are all zeros to restrict the number of bits to 10. Therefore, +0.999 becomes 0111 1111 1100 0000, and -1.000 becomes 1000 0000 0000 0000. This assignment allows the number, 0.000, to be represented by 0000 0000 0000 0000. The second number type is to represent unsigned fractions that change between +0.999 and 0.000 according to the next row in Fig 5.13. There are no sign bits in this numbering format, and the lower six bits are all zeros, again to restrict the number of bits to 10. Thus, +0.999 is represented by 1111 1111 1100 0000, and 0.000 by 0000 0000 0000 0000. The fraction, +0.500, scales in the middle and becomes 1000 0000 0000 0000. The third numbering system is the signed integer described in the third row in Fig. 5.13. The truth table in this section represents numbers between 511 and -512. Hence, the integer, 511 becomes 0000 0001 1111 1111, and -512 becomes 1111 1100 0000 0000. Again, the integer 0 can still be represented as 0000 0000 0000 0000. The fourth number type is the unsigned integer where integers between 1023 and 0 are represented according to the bottom row of Fig. 5.13. Thus, the integer, 1023, becomes 0000 0011 1111 1111 and 0 becomes 0000 0000 0000 0000. The integer in between 0 and 1023 such as 512 becomes 0000 0010 0000 0000.
5.5
Analog-to-Digital Converter in dspic33fj128mc802
Fig. 5.13 10-bit output format of ADC1
125
FORM = 11 Signed Fraction D8 D8 D7 D6 D5 D4 D3 D2 D1 D0
0
0
0
0
0
0
0111 1111 1100 0000 (+0.999)
0000 0000 0000 0000 (0.000)
1000 0000 0000 0000 (-1.000)
VREFL
input
VREFH
FORM = 10 Unsigned Fraction D9 D8 D7 D6 D5 D4 D3 D2 D1 D0
0
0
0
0
0
0
1111 1111 1100 0000 (+0.999)
1000 0000 0000 0000 (0.500)
0000 0000 0000 0000 (0.000)
VREFL
input
VREFH
FORM = 01 Signed Integer D8 D8 D8 D8 D8 D8 D8 D8 D7 D6 D5 D4 D3 D2 D1 D0
0000 0001 1111 1111 (511)
0000 0000 0000 0000 (0)
1111 1100 0000 0000 (-512)
VREFL
input
VREFH
FORM = 00 Unsigned Integer 0
0
0
0
0
0
D9 D8 D7 D6 D5 D4 D3 D2 D1 D0
0000 0011 1111 1111 (1023)
0000 0010 0000 0000 (512)
0000 0000 0000 0000 (0)
VREFL
input
VREFH
126
5
Data Converters
The 12-bit ADC output format also supports four different numbering systems as shown in Fig. 5.14. All four numbering systems in this figure are similar to the ones shown in Fig. 5.13. The topmost row shows a signed fraction number between +0.999 and -1.000 in 12 bits where the most significant bit is the sign bit, and the lower four bits are all zeros to restrict the number of data bits to 12. Therefore, +0.999 becomes 0111 1111 1111 0000 and -1.000 becomes 1000 0000 0000 0000. This assignment allows the number, 0.000, to be represented by 0000 0000 0000 0000. The second row shows the unsigned fraction that change between +0.999 and 0.000 in 12 bits where there is no sign bit. Again, the lower four bits are all zeros to limit the number of bits to 12. Thus, +0.999 is represented by 1111 1111 1111 0000, and 0.000 by 0000 0000 0000 0000. The fraction, +0.500, scales in the middle, and it becomes 1000 0000 0000 0000. The third row represents signed integer where most significant five bits are signextension bits. The integers between 2045 and -2046 are represented by 0000 0111 1111 1101 and 1111 1000 0000 0010, respectively. The integer 0 is still represented as 0000 0000 0000 0000. The last row shows the representation of an unsigned integer where the most significant four bits are all zeros. Integers up to 4095 are represented, using this format. Thus, the integer, 4095, becomes 0000 1111 1111 1111, and 0 becomes 0000 0000 0000 0000. The integer in between 0 and 1023 such as 512 becomes 0000 1000 0000 0000.
5.5
Analog-to-Digital Converter in dspic33fj128mc802
127
FORM = 11 Signed Fraction D10 D10
D9
D8
D7
D6
D5 D4 D3 D2 D1 D0
0
0
0
0
0
0
0
0111 1111 1111 0000 (+0.999)
0000 0000 0000 0000 (0.000)
1000 0000 0000 0000 (-1.000)
VREFL
input
VREFH
FORM = 10 Unsigned Fraction D11 D10
D9
D8
D7
D6
D5 D4 D3 D2 D1 D0
0
1111 1111 1111 0000 (+0.999)
1000 0000 0000 0000 (0.500)
0000 0000 0000 0000 (0.000)
VREFL
input
VREFH
FORM = 01 Signed Integer D10 D10 D10 D10 D10 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0 0000 0111 1111 1101 (2045)
0000 0000 0000 0000 (0)
1111 1000 0000 0010 (-2046)
VREFL
input
VREFH
FORM = 00 Unsigned Integer 0
0
0
0
D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0
0000 1111 1111 1111 (4095)
0000 1000 0000 0000 (2048)
0000 0000 0000 0000 (0)
Fig. 5.14 12-bit output format of ADC1
VREFL
input
VREFH
128
5
Data Converters
The ADC1 unit uses either the CPU clock, or an internal RC clock as shown in Fig. 5.15. The CPU clock frequency, FCY, can be divided by powers of 2 by the ADC conversion clock select bits, ADCS[7:0], in AD1CON3 register. Therefore, the conversion clock period, TAD = 1/FAD, can be anywhere between TCY and 64TCY. ADC internal RC clock
1 FAD
ADC conversion clock divider 1, 2, 3, 4….64
FCY
0
6
AD1CON3[15]
AD1CON3[7:0]
Fig. 5.15 Generation of ADC1 conversion clock period, TAD
The ADC conversion process consists of sampling an analog input within a sample period, and holding this value while performing conversion within the hold and conversion period as shown in Fig. 5.16. Switch
Switch
Analog Input
ADC
Capacitor
Sample period
Capacitor
Hold and conversion period
Fig. 5.16 Sample/Hold and Conversion events
The method of sampling and conversion described in Fig. 5.16 produces the timing diagram in Fig. 5.17 for a single analog signal at AN0. Here, the analog input is sampled at the CH0 first, and then directed to the ADC for conversion. Each converted output is sequentially is distributed to one of the 16 ADC registers, ADC1BUF0 to ADC1BUF15, in the ADC buffer. When this process is complete, the ADC1 raises the logic level at the DONE bit of the AD1CON1 register to
5.5
Analog-to-Digital Converter in dspic33fj128mc802
129
indicate the buffer is full. If the user program fails to read the contents of the buffer, new data will be overwritten in all 16 registers. If the Automatic Sampling mode is engaged by the ASAM bit in the AD1CON1 register, sampling starts right after the conversion of the previous sample takes place as shown in this timing diagram. The user program must allocate sufficient time for sampling before clearing the SAMP bit in the AD1CON1 register. Clearing the SAMP bit produces the conversion trigger signal, and initiates the conversion. However, the conversion trigger can also come from a selected peripheral as will be discussed later. Conversion trigger TSAMP
TSAMP
TSAMP
ADC clock TCONV
Input to CH0
AN0
TCONV
TCONV
AN0
AN0
ASAM
SAMP
ADC1BUF0
ADC1BUF1
ADC1BUF15
DONE
Fig. 5.17 Timing diagram to continuously sample one analog input, AN0, at CH0
ADC1 can also accomplish sampling and converting six different analog signals from AN0 to AN5, using a single channel at CH0 as shown by the waveform in Fig. 5.18. Similar to the previous timing diagram in Fig. 5.17, the ADC1 scans all six analog inputs, and stores each converted result in a different ADC1BUF register. After scanning all six outputs the ADC1 produces DONE = 1 signal.
130
5
Data Converters
Conversion trigger TSAMP
TSAMP
TSAMP
ADC clock TCONV
Input to CH0
TCONV
TCONV
AN1
AN0
AN5
ASAM
SAMP
ADC1BUF0
ADC1BUF1
ADC1BUF15
DONE
Fig. 5.18 Timing diagram to sequentially sample analog inputs, AN0 to AN5, at CH0
There are four control, three input-select, and one port configuration registers, which govern the configuration and operation of ADC1. The first control register, AD1CON1, is shown in Fig. 5.19. The ADON bit turns on the ADC1 when it is at logic 1. The ADSIDL bit discontinues the module operation in idle mode. The DMA buffer build mode bit, ADDMABM, writes data to DMA buffers in the order of conversion. The ADC1 unit provides the same address to the DMA buffer as it does to a stand-alone buffer. The bit responsible for 10-bit or 12-bit data from ADC1 is the AD12B bit. AD12B = 1 configures ADC1 as a 12-bit, single channel device. Otherwise, the device has a 10bit output, and operates with four channels. 9
15
14
13
12
11
10
ADON
-
ADSIDL
ADDMABM
-
AD12B
8
7
6
5
4
3
2
1
0
-
SIMSAM
ASAM
SAMP
DONE
FORM[1:0] SSRC[2:0]
Fig. 5.19 Contents of the control 1 register, AD1CON1
The FORM[1:0] bits determine the output of ADC1 to be a signed or unsigned fraction, signed or unsigned integer as shown in Table 5.3. The details of the 10-bit and 12-bit output data formats have already been explained in Figs. 5.13 and 5.14, respectively.
5.5
Analog-to-Digital Converter in dspic33fj128mc802
131
Table 5.3 The form entry, FORM[1:0], in AD1CON1 FORM[1:0] entry in AD1CON1 register For 10-bit operation: 11 = Signed fraction
(DOUT = sddd dddd dd00 0000)
10 = Unsigned fraction (DOUT = dddd dddd dd00 0000) 01 = Signed integer
(DOUT = ssss sssd dddd dddd)
00 = Unsigned integer (DOUT = 0000 00dd dddd dddd)
For 12-bit operation: 11 = Signed fraction
(DOUT = sddd dddd dddd 0000)
10 = Unsigned fraction (DOUT = dddd dddd dddd 0000) 01 = Signed integer
(DOUT = ssss sddd dddd dddd)
00 = Unsigned integer (DOUT = 0000 dddd dddd dddd)
The SSRC[2:0] bits determines the event that ends the sampling period, and starts the conversion as shown in Table 5.4. According to this table, an internal counter, a PWM motor control interval, a GP timer, an interrupt pin, or clearing the sample bit in the user program are considered possible events, and they all can end the sampling period. The SIMSAM bit refers to simultaneous sampling. When SIMSAM = 1, the ADC samples CH0, CH1, CH2 and CH3 simultaneously as shown in Fig. 5.10 (or only CH0 and CH1, depending on the CHPS[1:0] bits). When SIMSAM = 0, the channels are sampled in sequence as shown in Fig. 5.11. The ASAM bit refers to Automatic Sampling. This means when ASAM = 1, sampling begins as soon as a previous conversion ends. Otherwise, sampling halts until the SAMP bit is set in the user software. The SAMP bit is the sampling enable bit, and it works with the ASAM bit. If automatic sampling is engaged (ASAM = 1), the hardware spontaneously sets SAMP = 1, indicating an ongoing sampling process. If manual sampling is employed (ASAM = 0), then the user program needs to initiate the sampling process by SAMP = 1. SAMP = 0 indicates the sampling process is on hold. The DONE bit is a conversion status bit, and it indicates if the conversion cycle is completed or in progress.
132
5
Data Converters
Table 5.4 The Sample Source Clock entry, SSRC[2:0], in AD1CON1 SSRC[2:0] entry (Sample Source Clock) in AD1CON1 register 111 = Internal counter ends sampling and starts conversion 110 = Reserved 101 = Motor control PWM2 interval ends sampling and starts conversion 100 = GP timer (timer 5 for ADC1) compare ends sampling and starts conversion 011 = Motor control PWM1 interval ends sampling and starts conversion 010 = GP timer (timer 3 for ADC) compare ends sampling and starts conversion 001 = Active transition at INT0 pin ends sampling and starts conversion 000 = Clearing sample bit ends sampling and starts conversion
The AD1CON2 register is shown in Fig. 5.20. The VCFG[2:0] bits in this figure indicate the reference voltage levels used in the ADC1. According to Table 5.5, any combination of analog power supply (AVDD), analog ground (AVSS), or external reference voltage (Ext VREF) can be applied to the ADREF+ and ADREFterminals to produce the proper reference voltage. The Channel Scan A bit, CSCNA, determines whether the inputs at CH0 are to be scanned or not. CSCNA = 1 scans all the selected inputs to CH0. CSCNA = 0 omits scanning altogether. 15 14 13 12 11 -
-
10
9
CSCNA
VCFG[2:0]
7
6
BUFS
-
8
CHPS[1:0]
5
4
3
2
1
0
BUFM
ALTS
SMPI[3:0]
Fig. 5.20 Contents of the control 2 register, AD1CON2
Table 5.5 The Voltage Reference Configuration entry, VCFG[2:0], in AD1CON2 VCFG[2:0] entry (Voltage Reference Configuration) in AD1CON2 register ADREF+
ADREF-
1XX = AVDD
AVSS
011 = Ext VREF+
Ext VREF-
010 = AVDD
Ext VREF-
001 = Ext VREF+
AVSS
000 = AVDD
AVSS
5.5
Analog-to-Digital Converter in dspic33fj128mc802
133
The Channel Select bits, CHPS[1:0], shown in Table 5.6 selects the channels to perform conversion. Table 5.6 The Channel Select entry, CHPS[1:0], in AD1CON2 CHPS[1:0] entry (Channel Select) in AD1CON2 register 1X = Converts CH0, CH1, CH2, CH3 01 = Converts CH0, CH1 00 = Converts CH0
The Buffer Status bit, BUFS, indicates if ADC1 is in the process of filling output buffers, ADC1BUF0 to ADC1BUF7 (BUFS = 0), or ADC1BUF8 to ADC1BUF15 (BUFS = 1). The Sample Interrupt bits, SMPI[3:0], indicates how often ADC1 needs to generate an interrupt or DMA address according to the number of samples shown in Table 5.7. Table 5.7 The Sampling per Interrupt entry, SMPI[3:0], in AD1CON2 SMPI[3:0] entry (Sampling ops per Interrupt) in AD1CON2 register 1111 = Generate interrupt or generate DMA address after 16 samples 1110 = Generate interrupt or generate DMA address after 15 samples … 0000 = Generate interrupt or generate DMA address after every sample
The Buffer Fill Mode bit, BUFM, indicates the address of the buffer that ADC1 needs to fill. BUFM = 1 alternately fills the output buffer at the address 0x0 on the first interrupt, and the address 0x8 on the next interrupt. BUFM = 0 always starts filling the buffer at the address 0x0. Finally, the Alternate Sample bit, ALTS, selects either channel A during the first sample, channel B during the next sample, or constantly selects channel A according to Fig. 5.9. The next control register, AD1CON3, determines the nature of the ADC1 clock as shown in Fig. 5.21. The RC clock bit, ADRC, defines if an internal RC clock is used. An entry, ADRC = 0, uses the system clock, FCY, as shown in Fig. 5.15.
134
5 15 ADRC
14 13 12 11 10 -
9
8
7
6
5
4
3
2
1
Data Converters 0
-
SAMC[4:0]
ADCS[7:0]
Fig. 5.21 Contents of the control 3 register, AD1CON3
The Sampling Clock bits, SAMC[4:0], uses the number of conversion clock periods, TAD, for performing auto sampling. According to Table 5.8, the entire sampling period can be adjusted from one TAD to 31TAD. SAMC[4:0] = 0 entry refers to adjusting the sampling period in the user program (manual sampling). Table 5.8 The Auto Sample Time entry, SAMC[4:0], in AD1CON3 SAMC[4:0] entry (Auto Sample Time) in AD1CON3 register 11111 = 31 TAD 11110 = 30 TAD ... 00010 = 2 TAD 00001 = 1 TAD 00000 = 0 TAD
The Conversion Clock Select bits, ADCS[7:0], shown in Table 5.9 defines the length of the conversion period, TAD, in terms of the number of system clock periods, TCY, if the system clock is used instead of the internal RC clock (ADRC = 0). Table 5.9 The ADC clock select entry, ADCS[7:0], in AD1CON3 ADCS[7:0] entry (AD Conversion Clock Select ) in AD1CON3 register 11111111 = Reserved 01000000 = Reserved … 001111111 = 64 TCY = TAD ... 00000001 = 2 TCY = TAD 00000000 = TCY = TAD
5.5
Analog-to-Digital Converter in dspic33fj128mc802
135
Finally, the fourth control register, AD1CON4, shown in Fig. 5.22 indicates the size of the DMA buffers for each analog input. According to Table 5.10, the DMA buffer size can be changed from one word to 128 words for each analog input. Fig. 5.22 Contents of the control 4 register, AD1CON4
15 14 13 12 11 10 -
-
-
-
-
-
9
8
7
6
5
4
3
-
-
-
-
-
-
-
2
1
0
DMABL[2:0]
Table 5.10 The DMA Buffer Location entry, DMABL[2:0], in AD1CON4 DMABL[2:0] entry (DMA Buffer Locations per Analog Input) in AD1CON4 register 111 = Allocates 128 words of DMA buffer to each analog input 110 = Allocates 64 words of DMA buffer to each analog input 101 = Allocates 32 words of DMA buffer to each analog input 100 = Allocates 16 words of DMA buffer to each analog input 011 = Allocates 8 words of DMA buffer to each analog input 010 = Allocates 4 words of DMA buffer to each analog input 001 = Allocates 2 words of DMA buffer to each analog input 000 = Allocates 1 words of DMA buffer to each analog input
There are two input select registers that define the positive and negative inputs of the four analog comparators in Fig. 5.9: one that configures CH0, and the other that configures CH1, CH2 and CH3. The CH0 configuration register, AD1CHS0, is shown in Fig. 5.23. In this register, the CH0 negative input select bit for sample B, CH0NB, selects AN1 for the negative input of the comparator when CH0NB = 1 according to Fig. 5.9. If CH0NB = 0, the negative input becomes VREF-. The CH0 positive input select bits for sample B, CH0SB[4:0], in Table 5.11 select one of the analog inputs, AN0 to AN5, to be the positive input for the comparator that forms CH0. According to the selection criteria by CH0NB and CH0SB[4:0], it is possible to select AN1 for both the positive and negative inputs of the comparator. However, in most cases one of the six analog inputs, AN0 to AN5, forms the positive input, while VREF- forms the negative input. 15 CH0NB
14 13 12 11 10 -
9
-
8
7
6
5
CH0NA
-
-
CH0SB[4:0]
Fig. 5.23 Contents of the channel select 0 register, AD1CHS0
4
3
2
1
CH0SA[4:0]
0
136
5
Data Converters
Table 5.11 The Channel 0 select B entry, CH0SB[4:0], in AD1CHS0 CH0SB[4:0] entry in AD1CHS0 register 00101 = Channel 0 positive input is AN5 00100 = Channel 0 positive input is AN4 00011 = Channel 0 positive input is AN3 00010 = Channel 0 positive input is AN2 00001 = Channel 0 positive input is AN1 00000 = Channel 0 positive input is AN0
The same is true for sample A. The CH0 negative input select bit for sample A, CH0NA, selects AN1 for the negative input of the comparator when CH0NA = 1, and VREF- when CH0NA = 0. The positive input select bits for sample A, CH0SA [4:0], in Table 5.12 select one of the analog inputs, AN0 to AN5, to be the positive input for the comparator. Table 5.12 The Channel 0 select A entry, CH0SA[4:0], in AD1CHS0 CH0SA[4:0] entry in AD1CHS0 register 00101 = Channel 0 positive input is AN5 00100 = Channel 0 positive input is AN4 00011 = Channel 0 positive input is AN3 00010 = Channel 0 positive input is AN2 00001 = Channel 0 positive input is AN1 00000 = Channel 0 positive input is AN0
The next input select register, AD1CHS123, only defines the positive inputs of the comparators for CH1, CH2 and CH3 as shown in Fig. 5.24. The negative inputs of these three comparators are permanently connected to VREF-. Channel 1, 2 and 3 positive input select bit for sample B, CH123SB, selects AN3 for CH1, AN4 for CH2, and AN5 for CH3 if CH123SB = 1; AN0 for CH1, AN1 for CH2, and AN2 for CH3 if CH123SB = 0. Similarly, Channel 1, 2 and 3 positive input select bit for sample A, CH123SA, selects AN3 for CH1, AN4 for CH2, and AN5 for CH3 if CH123SA = 1; this bit also selects AN0 for CH1, AN1 for CH2, and AN2 for CH3 if CH123SA = 0.
5.5
Analog-to-Digital Converter in dspic33fj128mc802 15 14 13 12 11 10
9
-
-
-
-
-
-
-
8 CH123SB
137
7
6
5
4
3
2
1
0
-
-
-
-
-
-
-
CH123SA
Fig. 5.24 Contents of the channel select 1, 2 and 3 register, AD1CHS123
The Channel Scan Select register, AD1CSSL, is shown in Fig. 5.25. The only entry in this register, CSS[5:0], selects the analog inputs, AN0 to AN5, to be scanned for sampling and conversion according to Table 5.13. The detailed channel scanning mechanism that contains all four channels is previously shown in Fig. 5.11. Fig. 5.25 Contents of the channel scan select register, AD1CSSL
15 14 13 12 11 10 -
-
-
-
-
9
8
7
6
-
-
-
-
-
5
4
3
2
1
0
CSS[5:0]
Table 5.13 The Channel Scan Select entry, CSS[5:0], in AD1CSSL CSS[5:0] entry (Channel Scan Select) in AD1CSSL register CSS[5] – Channel Scan Select for AN5 1 = Select AN5 for Channel Scan 0 = Skip AN5 for Channel Scan ... CSS[0] – Channel Scan Select for AN0 1 = Select AN0 for Channel Scan 0 = Skip AN0 for Channel Scan
The last register configures the input ports for ADC1 as shown in Fig. 5.26. According to Table 5.14, logic 0 entry in PCFG[5:0] configures each input, AN0 to AN5, to be an analog port. Logic 1 entry makes each input a digital input pin. Fig. 5.26 Contents of port configuration register, AD1PCFGL
15 14 13 12 11 10 -
-
-
-
-
-
9
8
7
6
-
-
-
-
5
4
3
2
1
PCFG[5:0]
0
138
5
Data Converters
Table 5.14 The Port Configuration entry, PCFG[5:0], in AD1PCFGL PCFG[5:0] entry (Port Configuration) in AD1PCFGL register PCFG[5] –Port Configuration for AN5 1 = Port pin in Digital mode 0 = Port pin in Analog mode ... PCFG[0] –Port Configuration for AN0 1 = Port pin in Digital mode 0 = Port pin in Analog mode
Program 5.1 shows the first example how to configure and operate ADC1 in manual mode [2]. The instruction, breakpoint = 1, stops the program in order to look at the contents of ADC1BUF0, ADCValue and OutputVoltage in the Watch window in MPLABX. Program 5.1 // ADC is in manual sampling mode and samples ONCE with a single FRC oscillator #include // Configuration Register - FOSCSEL #pragma config IESO = 0 // start with a user-selected oscillator at reset #pragma config FNOSC = 7 // select FRC oscillator with Divide-N at reset // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled int main () { // OSCTUN Register OSCTUNbits.TUN = 0;
// select FRC = 7.37MHz
// CLKDIV Register CLKDIVbits.FRCDIV = 6; // FRCDIVN = FRC/64 = 7.37/64 = 115.15KHz = FOSC CLKDIVbits.DOZE = 1; // FOSC/4 = 115.15/4 = 28.8KHz = FCY CLKDIVbits.DOZEN = 1; // DOZE defines the ratio between cpu and peripheral clocks // AD1CON1 Register AD1CON1bits.ADON = 0; // during configuration, don't turn on ADC AD1CON1bits.AD12B = 0; // 10-bit ADC mode AD1CON1bits.FORM = 0; // 10-bit integer output, no sign bit AD1CON1bits.SSRC = 0; // Manual sampling AD1CON1bits.SIMSAM = 0; // No simultaneous sampling AD1CON1bits.ASAM = 0; // No auto-sampling AD1CON1bits.SAMP = 0; // During configuration, don't sample
5.5
Analog-to-Digital Converter in dspic33fj128mc802
139
// AD1CON2 Register AD1CON2bits.VCFG = 0; // VREF+ = VDD, VREF- = VSS AD1CON2bits.CSCNA = 0; // Don't scan inputs of CH0 AD1CON2bits.CHPS = 0; // Select only CH0 for conversion AD1CON2bits.SMPI = 0; // Generate interrupt after every sample-convert operation AD1CON2bits.BUFM = 0; // Start filling the buffer from the start address AD1CON2bits.ALTS = 0; // Don't alternate between sample A and sample B // AD1CON3 Register AD1CON3bits.ADRC = 0; // ADC clock is derived from cpu clock - TCY AD1CON3bits.SAMC = 0; // No number of TADs during sampling - manual sampling
// AD1CON4 Register AD1CON4bits.DMABL = 0; // Allocate 1 word DMA buffer for each analog input - not applicable // AD1CHS0 Register AD1CHS0bits.CH0NB = 0; // CH0 neg. port for sample B is VSS - not applicable AD1CHS0bits.CH0SB = 0; // CH0 pos. port for sample B is AN0 - not applicable AD1CHS0bits.CH0NA = 0; // CH0 neg. port for sample A is VREF- = VSS AD1CHS0bits.CH0SA = 0; // CH0 pos. port for sample A is AN0 // AD1CSSL Register AD1CSSL = 0x0000; // Skip scanning AN0 through AN15 // AD1PCFGL Register AD1PCFGL = 0xFFFE; TRISAbits.TRISA0 = 1;
// Pin AN0 is assigned as analog pin // Pin AN0 is assigned as input
// Operate ADC - Manual sampling unsigned int ADCValue, Delay, breakpoint; float OutputVoltage; ADCValue = 0; // Reset initial ADC value AD1CON1bits.ADON = 1; // Turn on the ADC AD1CON1bits.SAMP = 1; // Start sampling // Wait for the capacitor to charge for (Delay = 0; Delay < 1000; Delay++); AD1CON1bits.SAMP = 0;
// Stop sampling after the delay and start conversion
while (AD1CON1bits.DONE != 1); // Wait until DONE = 1 for the end of conversion ADCValue = ADC1BUF0; // Transfer the 16-bit result in BUF0 to ADCValue OutputVoltage = (float)ADCValue * ((float)3.3 / (float)1024.0); // Calculate ADC voltage breakpoint = 1; }
140
5
Data Converters
In this program, an analog voltage is applied to AN0 pin, sampled and converted into a 10-bit unsigned integer manually. There are four sections in this program. The first section uses pragma statements to choose a single oscillator setup during system booting and operation. The #pragma config IESO = 0 statement disables any internal-to-external oscillator switch-over mechanism. The statement, #pragma config FNOSC = 7, defines the data-path for an internal FRC oscillator with FRCDIVN output to generate the system clock. The #pragma config FCKSM = 3 statement turn off the clock switching and fail-safe modes. The #pragma config OSCIOFNC = 0 statement makes the OSCO terminal a general-purpose I/O pin. Since the system does not use a secondary HS, XT or EC-type external crystal, the statement, #pragma config POSCMD = 3, disables all primary oscillators. Furthermore, the OSCTUNbits.TUN = 0 statement tunes the internal FRC clock at 7.37 MHz. The CLKDIV register basically divides the clock frequency to define the CPU and peripheral clock frequencies. Since there is only one internal clock without any switch-over option, no mechanism to recover the clock is needed. This entails to the statement, CLKDIVbits.ROI = 0, to disable all the interrupts during clock recovery. The statement, CLKDIVbits.FRCDIV = 6, divides the 7.37 MHz FRC clock frequency by 64 and produces 115.15 KHz at FOSC. The CLKDIVbits. DOZEN = 1 statement enables the DOZE unit. Finally, the CLKDIVbits.DOZE = 1 statement divides the clock at FOSC by four, and generates FCY = 115.15/ 4 = 28.8 KHz for the system. The second section configures a single I/O port, AN0, to be an analog input port as shown in Fig. 5.27. In this figure, the TRISAbits.TRISA0 = 1 statement assigns AN0 to be an input pin. The port configuration statement, AD1PCFGL = 0xFFFE, connects the AN0 input to ADC1. // ADC is operating in manual mode #include … // AD1PCFGL Register AD1PCFGL = 0xFFFE; TRISAbits.TRISA0 = 1;
// Pin AN0 is assigned as analog pin // Pin AN0 is assigned as input
... }
Fig. 5.27 I/O configuration for manual ADC operation
The configuration of ADC1 for manual mode is shown in Fig. 5.28. In this figure, three control, one channel select, and one channel scan registers define the operation of ADC1.
5.5
Analog-to-Digital Converter in dspic33fj128mc802
141
The first control register to program is the AD1CON1 register. In this register, the AD1CON1bits.ADON = 0 statement keeps ADC1 not operational during the configuration phase. When the configuration cycle is complete, we turn on ADC1 to sample and convert an analog signal at AN0. The AD1CON1bits.AD12B = 0 statement engages the 10-bit ADC1 output. The AD1CON1bits.FORM = 0 statement produces this 10-bit output to be an unsigned integer format as shown in Table 5.3. The statement, AD1CON1bits.SSRC = 0, expects the user program to clear the sample bit, and start conversion in manual mode as shown in Table 5.4 and Fig. 5.17. The AD1CON1bits.SIMSAM = 0 statement initiates sequential sampling in ADC1 as shown in Fig. 5.11. The AD1CON1bits.ASAM = 0 statement prevents ADC1 to perform automatic sampling upon the completion of a prior data conversion. Finally, AD1CON1bits.SAMP = 0 pauses sampling until all ADC1 registers are programmed. The second control register, AD1CON2, configures operational channels and the reference voltage levels for ADC1. Therefore, the statement, AD1CON2bits. VCFG = 0, defines analog VDD to be applied to VREF+ terminal, and analog ground (VSS) to be applied to VREF- terminal. The channel scan statement, AD1CON2bits.CSCNA = 0, omits scanning the analog inputs to CH0 because there is only one analog input at AN0. The channel select input statement, AD1CON2bits.CHPS = 0, is an important statement, and it defines only the analog inputs to be sampled and converted at CH0. The remaining AD1CON2 statements are less important for the operation of ADC1 in manual mode. The AD1CON2bits. SMPI = 0 statement generates an interrupt after each sample and convert cycle. However, the application part of this program does not use interrupts. The AD1CON2bits.BUFM = 0 statement starts filling the output buffer at the address 0x0 when the first interrupt arrives as well as the next, and again it is not relevant to this case because the application program does not use interrupts. Finally, the AD1CON2bits.ALTS = 0 statement stops alternating between channel A during the first sample and channel B during the next sample but constantly selects channel A. The third control register, AD1CON3, defines the sampling period and conversion clock. The AD1CON3bits.ADRC = 0 statement produces the ADC1 clock from the system clock, FCY. The AD1CON3bits.SAMC = 0 statement refers to adjusting the sampling period through the user program. Finally, the AD1CON3bits. ADCS = 2 statement defines the length of the conversion period, TAD, to be three system clock periods long, 3TCY. The channel select register defines the positive and negative terminals of the comparator for each channel. Therefore, the AD1CHS0bits.CH0NA = 0 and AD1CHS0bits.CH0SA = 0 statements connect the negative port of the comparator to VREF- or analog ground (AVSS), and the positive port to the AN0 pin, respectively. The channel scan register statement, AD1CSSL = 0x0000, prevents scanning AN0 through AN5. However, this statement is not applicable to this case since we only use AN0 to sample and convert an analog signal.
142
5
Data Converters
// ADC is operating in manual mode #include … // Configure ADC for manual sampling AD1CON1bits.ADON = 0; // during configuration, don't turn on ADC AD1CON1bits.AD12B = 0; // 10-bit ADC mode AD1CON1bits.FORM = 0; // 10-bit integer output, no sign bit AD1CON1bits.SSRC = 0; // Manual sampling AD1CON1bits.SIMSAM = 0; // No simultaneous sampling AD1CON1bits.ASAM = 0; // No auto-sampling AD1CON1bits.SAMP = 0; // During configuration, don't sample AD1CON2bits.VCFG = 0; AD1CON2bits.CSCNA = 0; AD1CON2bits.CHPS = 0; AD1CON2bits.SMPI = 0; AD1CON2bits.BUFM = 0; AD1CON2bits.ALTS = 0;
// VREF+ = VDD, VREF- = VSS // Don't scan inputs of CH0 // Select only CH0 for conversion // Generate interrupt after every sample-convert operation // Start filling the buffer from the start address // Don't alternate between sample A and sample B
AD1CON3bits.ADRC = 0; // ADC clock is derived from cpu clock - TCY AD1CON3bits.SAMC = 0; // No number of TADs during sampling - manual sampling AD1CON3bits.ADCS = 2; // TAD = 3TCY AD1CHS0bits.CH0NA = 0; // CH0 neg. port for sample A is VREF- = VSS AD1CHS0bits.CH0SA = 0; // CH0 pos. port for sample A is AN0 AD1CSSL = 0x0000; // Skip scanning AN0 through AN15 ...
Fig. 5.28 ADC1 setup for manual sampling and conversion
The operation of ADC1 in manual mode is shown in Fig. 5.29. The contents of a 16-bit register, ADCValue, are initially cleared. Then ADC1 is turned on by AD1CON1bits.ADON = 1, and manual sampling is started by AD1CON1bits. SAMP = 1. This only makes sense because the sampling mode has already been defined to be manual by the AD1CON1bits.SSRC = 0 statement during ADC1 configuration. The application program waits for a certain period of time so that the capacitor at the output of CH0 reaches the new analog voltage value from AN0. After this delay, we stop sampling further by AD1CON1bits.SAMP = 0, and wait for the end of conversion, using the while loop, while (AD1CON1bits.DONE != 1). When the DONE bit sets in the AD1CON1 register, then we transfer the contents of the data buffer, ADC1BUF0, to the temporary register, ADCValue, that we initially
5.5
Analog-to-Digital Converter in dspic33fj128mc802
143
cleared. Since this is a 10-bit unsigned integer, we convert it to a floating point number by the statement, OutputVoltage = (float)ADCValue * ((float)3.3 / (float) 1024.0), which produces a number between 0 V and 3.3 V. This last statement is an error checking step, which should reproduce the analog voltage applied to ADC1 prior to manual conversion. // ADC is operating in manual mode #include … // Operate ADC - Manual sampling unsigned int ADCValue, Delay; float OutputVoltage; ADCValue = 0; // Reset initial ADC value AD1CON1bits.ADON = 1; // Turn on the ADC AD1CON1bits.SAMP = 1; // Start sampling // Wait for the capacitor to charge for (Delay = 0; Delay < 1000; Delay++); AD1CON1bits.SAMP = 0;
// Stop sampling after the delay and start conversion
while (AD1CON1bits.DONE != 1); // Wait until DONE = 1 for the end of conversion ADCValue = ADC1BUF0; // Transfer the 16-bit result in BUF0 to ADCValue OutputVoltage = (float)ADCValue * ((float)3.3 / (float)1024.0); // Calculate ADC voltage }
Fig. 5.29 ADC1 operation in manual mode
In order to test the actual analog voltage readings at the AN0 pin, the user program is stopped by inserting another statement, breakpoint = 1, following the last expression, OutputVoltage. Clicking at the instruction number on the left side of this statement (87, in this case) with the left mouse button highlights the entire instruction in red in MPLABX window as shown in Fig. 5.30. When the program runs, it executes every instruction in the program, including the floating point number, OutputVoltage, and the instruction, breakpoint = 1, until it stops at the highlighted green line. At this point, the program waits for the user prompt to continue, and this is the perfect instance to extract the value that resides in the output buffer for ADC1, ADC1BUF0, or the variable, OutputVoltage, to see the actual voltage at the AN0 pin.
144
5
Data Converters
Fig. 5.30 MPLABX Watch window to see the values of ADC1BUF0 and OutputVoltage
To see these values, the user needs to activate the watch window feature in MPBAX. To do this, we first click on the “Window” button on MPLABX window, then select the “Debugging” option, and then the “Watches” option. The user dialog interface at the bottom of the MPLABX window will change to show the “Watches” entry. To enter a new watch such as the contents of ADC1BUF0, we left click the mouse button on the area, , until this label disappears, and write ADC1BUF0. The name of the watch entry along with the type, value etc. will be displayed after the name of the Watch is entered. The other variables, such as ADCValue, OutputVoltage etc., can similarly be entered as new Watches. The properties of the Watch entries can be changed by right clicking the mouse button on the name entry (or any other existing entry) on top of the user dialog window. Type, value, binary, hexadecimal or decimal options can be selected and even the variable address can be extracted on the pop window. In this example, ADC1BUF0 contains 00000010 10001101 and OutputVoltage ≈ 2.10 V as seen on Fig. 5.30. The second example operates ADC1 in automatic mode as shown in Program 5.2. In this program, an analog voltage is applied to AN0 pin, sampled and converted into a 10-bit unsigned integer automatically using three control and one channel select registers as shown in Fig. 5.31. The instruction, breakpoint = 1, stops the program so that the user can examine the contents of AD1BUF0, ADCValue and OutputVoltage in MPLABX.
5.5
Analog-to-Digital Converter in dspic33fj128mc802
145
Program 5.2 // ADC is in AUTO sampling mode ONCE with a single FRC oscillator #include // Configuration Register - FOSCSEL #pragma config IESO = 0 // start with a user-selected oscillator at reset #pragma config FNOSC = 7
// select FRC oscillator with Divide-N at reset
// Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3
// primary oscillators are disabled
int main () { // OSCTUN Register OSCTUNbits.TUN = 0;
// select FRC = 7.37MHz
// CLKDIV Register CLKDIVbits.FRCDIV = 6;
// FRCDIVN = FRC/64 = 7.37/64 = 115.15KHz = FOSC
CLKDIVbits.DOZE = 1; CLKDIVbits.DOZEN = 1;
// FOSC/4 = 115.15/4 = 28.8KHz = FCY // DOZE defines the ratio between cpu and peripheral clocks
// AD1CON1 Register AD1CON1bits.ADON = 0; AD1CON1bits.AD12B = 0;
// during configuration, don't turn on ADC // 10-bit ADC mode
AD1CON1bits.FORM = 0;
// 10-bit integer output, no sign bit
AD1CON1bits.SSRC = 7; // Internal counter ends sampling and starts conversion AD1CON1bits.SIMSAM = 0; // No simultaneous sampling AD1CON1bits.ASAM = 1; AD1CON1bits.SAMP = 0;
// Auto-sampling // During configuration, don't sample
// AD1CON2 Register AD1CON2bits.VCFG = 0;
// VREF+ = VDD, VREF- = VSS
AD1CON2bits.CSCNA = 0; // Don't scan inputs of CH0 AD1CON2bits.CHPS = 0; // Select only CH0 for conversion AD1CON2bits.SMPI = 0; AD1CON2bits.BUFM = 0;
// Generate interrupt after every sample-convert operation // Start filling the buffer at address 0x0
AD1CON2bits.ALTS = 0;
// Don't alternate between sample A and sample B
// AD1CON3 Register AD1CON3bits.ADRC = 0; // ADC clock is derived from cpu clock - TCY AD1CON3bits.SAMC = 16; // TSAMP = 16*TAD AD1CON3bits.ADCS = 2;
// TAD = 3*TCY
146
5
Data Converters
// AD1CHS0 Register AD1CHS0bits.CH0NA = 0; // CH0 negative port for sample A is VREF- = VSS AD1CHS0bits.CH0SA = 0; // CH0 positive port for sample A is AN0 // AD1PCFGL Register AD1PCFGL = 0xFFFE; TRISAbits.TRISA0 = 1;
// Pin AN0 is assigned as analog pin // Pin AN0 is assigned as input
// Operate ADC - Auto sampling unsigned int ADCValue, breakpoint; float OutputVoltage; ADCValue = 0; AD1CON1bits.ADON = 1;
// Reset initial ADC value // Turn on the ADC
while (AD1CON1bits.DONE != 1); // Wait until DONE = 1 to the end of conversion ADCValue = ADC1BUF0;
// Transfer the 16-bit result in BUF0 to ADCValue
OutputVoltage = (float)3.3 * ((float)ADCValue / (float)1024.0); // Calculate ADC voltage breakpoint = 1; }
Similar to the manual operation, the AD1CON1bits.ADON = 0 statement blocks ADC1 operation during the configuration phase. The AD1CON1bits.AD12B = 0 and AD1CON1bits.FORM = 0 statements produce a 10-bit ADC1 output in unsigned integer format. The AD1CON1bits.SSRC = 7 statement initiates an auto sampling operation. An internal counter automatically stops sampling and starts conversion. The statement, AD1CON1bits.SIMSAM = 0, blocks any simultaneous sampling as shown in Fig. 5.10. The AD1CON1bits.ASAM = 1 statement engages automatic sampling after the completion of each conversion. Finally, AD1CON1bits.SAMP = 0 halts sampling until all ADC1 registers are programmed. The second control register, AD1CON2, configures the operational channels and the reference voltage levels for ADC1 prior to automatic mode of operation. This register is programmed exactly the same way as the manual case. Therefore, ADC1 will have analog VDD at the VREF+ and analog ground at the VREF- terminals. Channel scanning will be omitted for the selected channel, CH0; interrupts will be generated after each conversion; output data buffer will start filling from the address 0x0 after every interrupt; and there will be no alternation between sample A and sample B during the ADC1 operation.
5.5
Analog-to-Digital Converter in dspic33fj128mc802
147
According to the statements in the third control register, AD1CON3, ADC1 will use a clock derived from the system clock, FCY. The entire sampling period will be 16 conversion clock periods long or 16TAD by AD1CON3bits.SAMC = 16, and each conversion clock period, TAD, will be three system clock periods long or 3TCY by AD1CON3bits.ADCS = 2. The channel select register will still connect the negative port of the comparator to VREF- or analog ground (AVSS), and the positive port to the AN0 pin. // ADC is operating in automatic mode #include … // Configure ADC for auto sampling AD1CON1bits.ADON = 0; // during configuration, don't turn on ADC AD1CON1bits.AD12B = 0; // 10-bit ADC mode AD1CON1bits.FORM = 0; // 10-bit integer output, no sign bit AD1CON1bits.SSRC = 7; // Internal counter ends sampling and starts conversion AD1CON1bits.SIMSAM = 0; // No simultaneous sampling AD1CON1bits.ASAM = 1; // Auto-sampling AD1CON1bits.SAMP = 0; // During configuration, don't sample AD1CON2bits.VCFG = 0; // VREF+ = VDD, VREF- = VSS AD1CON2bits.CSCNA = 0; // Don't scan inputs of CH0 AD1CON2bits.CHPS = 0; // Select only CH0 for conversion AD1CON2bits.SMPI = 0; // Generate interrupt after every sample-convert operation AD1CON2bits.BUFM = 0; // Start filling the buffer at address 0x0 AD1CON2bits.ALTS = 0; // Don't alternate between sample A and sample B AD1CON3bits.ADRC = 0; // ADC clock is derived from cpu clock - TCY AD1CON3bits.SAMC = 16; // TSAMP = 16*TAD AD1CON3bits.ADCS = 2; // TAD = 3*TCY AD1CHS0bits.CH0NA = 0; // CH0 negative port for sample A is VREF- = VSS AD1CHS0bits.CH0SA = 0; // CH0 positive port for sample A is AN0 …
Fig. 5.31 ADC1 setup for automatic sampling and conversion
148
5
Data Converters
The operation of ADC1 in automatic mode is shown in Fig. 5.32. In this figure, a temporary register, ADCvalue, is initially cleared by ADCValue = 0. Subsequently, the statement, AD1CON1bits.ADON = 1, turns on ADC1. As soon as ADC1 turns on, the unit samples the analog voltage value at AN0, and automatically converts this value into a 10-bit unsigned integer format. The while statement, while (AD1CON1bits.DONE != 1), waits for the end of the conversion before the user program transfers the contents of the 10-bit result in ADC1BUF0 to ADCValue. Subsequently, this value is converted to a floating point number by OutputVoltage = (float)ADCValue * ((float)3.3 / (float) 1024.0) to check if the analog voltage applied to AN0 matches the converted number in ADC1BUF0. ADC is operating in automatic mode #include … // Operate ADC - Auto sampling unsigned int ADCValue; float OutputVoltage; ADCValue = 0; AD1CON1bits.ADON = 1;
// Reset initial ADC value // Turn on the ADC
while (AD1CON1bits.DONE != 1); // Wait until DONE = 1 to the end of conversion ADCValue = ADC1BUF0; // Transfer the 16 -bit result in BUF0 to ADCValue OutputVoltage = (float)3.3 * ((float)ADCValue / (float)1024.0); // Calculate ADC voltage }
Fig. 5.32 ADC1 operation in automatic mode
Similar to the MPLABX example in ADC1 manual mode, the values of multiple variables can be observed in the Watch window in MPLABX when ADC1 is running in auto sample mode as shown in Fig. 5.33. In this case, the Special Function Register (SFR), ADC1BUF0, contains the binary value, 00000001 11100010, while the unsigned integer, ADCValue, becomes slightly different from ADC1BUF0 at 00000001 11100101 because of the ADC1’s resolution limit. The variable, OutputVoltage, is approximately 1.56 V as the value of the analog voltage applied to the AN0 pin.
5.6
Sample ADC Projects
149
Fig. 5.33 MPLABX Watch window with ADC1BUF0, ADCValue and OutputVoltage
5.6
Sample ADC Projects
Project 1 In this example, a variable power supply that changes between 0 V and 3 V is connected to the AN0 pin. The output of the 16-bit ADC1BUF0 register has to be connected to the RB[15:0] pins in Little Endian format where each RB-pin becomes an output pin. An LED is connected to each RB-pin. As the analog voltage at AN0 increases from 0 V to 3 V in small increments, LEDs at RB[15:0] turn on in a pattern equal to ADC1BUF0. This value is also compared against ADC1BUF0 observed on the Watch window in MPLABX. Solution First, the directionality of RB pins are set to be all outputs by the statement, TRISB = 0. Second, the contents of ADC1BUF0 are written to the LATB register which has bus access because there is no direct bus access for ADC1BUF0. The program stops at the statement, breakpoint = 1, so that we can see the contents of ADC1BUF0 through the Watch window as shown in Fig. 5.34. For a voltage
150
5
Data Converters
approximately equal to 1.5 V at AN0, ADC1BUF0 produced 00000001 11010011. When we looked at the LEDs visually or inspect the voltage values with a multi meter, each LED from RB0 to RB15 matched the value in ADC1BUF0 except RB0, which was set to logic 0 instead of logic 1.
Fig. 5.34 MPLABX window showing how to read ADC1BUF0 to RB[15:0]
Project 2 In this experiment, we used an analog distance measuring sensor from Sharp Inc., GP2Y0A02, which consists of an Infrared (IR) emitting diode and a matching IR detector to measure distances between 4 cm and 100 cm [3]. The sensor normally needs +5 V to operate; although a 3.3 V operation is possible according to the datasheet. The distance measuring sensor and a reflective white surface board were mounted on separate stands as shown in Fig. 5.35. We changed the distance between the sensor and the target, and recorded each analog voltage produced at the sensor output with a multi meter as the distance was changed from 4 cm to 100 cm. This constitutes a calibration curve shown in Fig. 5.36.
5.6
Sample ADC Projects
151
Fig. 5.35 Setup showing how to calibrate Sharp GP2Y0A02 sensor to measure distances
The signal output from the sensor is connected to the AN0 pin of the dspic33fj128mc802 when ADC1 is running in manual sample mode. An application program running in MPLABX produces a value at RB[15:0] every time the target is moved away from the sensor. We compared each measured distance from the Watch window against the actual calibration curve we obtained earlier. 3
2.5 Line 2 Line 1 (invalid)
Volt (V)
2
Line 3
1.5
Line 4
1
Line 5 0.5
0 0
20
40
60 Distance (cm)
80
Fig. 5.36 Calibration curve for Sharp GP2Y0A02 distance measurement sensor
100
152
5
Data Converters
The calibration curve in Fig. 5.36 is approximated by a set of piecewise linear equations as shown below. According to these results, the IR detector safely operates for distances above 13 cm. If 1.60 < Voltage < 2.67 (Line 1) then Distance = 8.4*Voltage – 9.5 (invalid region) If 1.70 < Voltage < 2.67 (Line 2) then Distance = -19.6*Voltage + 65.3 If 1.10 < Voltage < 1.70 (Line 3) then Distance = -30.0*Voltage + 83.0 If 0.78 < Voltage < 1.10 (Line 4) then Distance = -68.7*Voltage + 125.6 If 0.55 < Voltage < 0.78 (Line 5) then Distance = -121.7*Voltage + 167.0 These equations were entered in the application program in Fig. 5.37. Next, we turned on the sensor and placed the target about 20 cm from the sensor. When the program stopped at the breakpoint, we extracted the ADCValue which resided in ADC1BUF0, voltage at the output of the sensor, and the distance from the Watch window. We found the calculated distance to be 26.06 cm as opposed to the measured distance of 26 cm. However, for longer distances in the neighborhood of 100 cm we found the error close to 10%.
Fig. 5.37 Running Sharp GP2Y0A02 distance measurement sensor on MPLABX
5.8
5.7
Digital-to-Analog Converter in dspic33fj128mc804
153
Introduction to Digital-to-Analog Converter (DAC)
The most common DAC utilizes the weighted summation method of digital inputs. In the example in Fig. 5.38, this results in a three-bit DAC with a weighted binary adder. In the three-bit DAC example above, the circuit is composed of two parts. The first part is an analog adder which adds all three binary input bits: IN[2] at the Most Significant Bit (MSB) position, IN[1] at the Mid Bit (MB) position, and IN[0] at the Least Significant Bit (LSB) position. This produces an output, ADDOUT = - (0.5 IN [2] + 0.25 IN[1] + 0.125 IN[0]) according to the equation in Fig. 5.9. The second part inverts ADDOUT, and forms OUT = - ADDOUT. When these two parts are serially combined, the circuit produces OUT = 0.5 IN[2] + 0.25 IN[1] + 0.125 IN[0], where each binary value at IN[2:0] is multiplied by the coefficients, 2-1, 2-2 and 2-3, respectively, before they are added. For example, the combination of IN[2] = 1, IN[1] = 0 and IN[0] = 1, with +5 V as logic 1 and 0 V as logic 0 generates OUT = 2.5 + 0.625 = 3.125 V. Similarly, the generation of all other analog outputs follows Table 5.2. R
2R IN[2]
R 4R
IN[1]
R ADDOUT
8R IN[0]
Weighted Binary Adder
ADDOUT = = OUT =
R
IN[2]
R
IN[1]
OUT
Analog Inverter with Unity Gain
R
IN[0]
2R
4R
8R
0.5 IN[2]
0.25 IN[1] 0.125 IN[0]
ADDOUT = 0.5 IN[2] + 0.25 IN[1] + 0.125 IN[0]
Fig. 5.38 Three-bit DAC schematic with weighted binary adder
5.8
Digital-to-Analog Converter in dspic33fj128mc804
The dspic33fj128mc802 IC does not have any onboard Digital-to-Analog Converter (DAC). Any conversion to analog domain needs to be done with an external DAC. However, the bigger sibling of dspic33fj128mc802, dspic33fj128mc804, has a dual channel DAC whose schematic is shown in Fig. 5.39. According to this schematic, data is stored in the Right and Left channel Data buffers, DAC1RDAT and
154
5
Data Converters
RFULL REMPTY
DAC1LDAT, as it becomes available from the data bus. Each buffer has four 16-bit registers. The RFULL and LFULL bits in the status register, DAC1STAT, show the status of the Right and the Left channel buffers when they are full. Similarly, REMPTY and LEMPTY bits in the status register indicate all four registers in the data buffer are empty so that the user program will retrieve new data.
DAC1RDAT Empty RMVOEN 0 DAC1RM (right midpoint) ROEN
1
DAC1RDAT
DAC
DAC1RP (right positive)
Databus[15:0]
AMP DAC1RN (right negative)
CONTROL
ACLK DACFDIV[6:0]
CLKDIV
DACDFLT
LMVOEN DAC1LM (left midpoint) LOEN DAC
DAC1LDAT
1
DAC1LP (left positive) AMP DAC1LN (left negative)
0 DAC1LDAT Empty
LEMPTY LFULL
Fig. 5.39 dspic33fj128mc804 2-channel DAC
When both data registers are empty, and no new data is received from the memory, DAC1 fetches the old data from the default buffer, DACDFLT. Regardless of the data source, the two DAC channels convert the digital data into analog form, and produce the right and left channel outputs in Fig. 5.39. The format of DAC1 output data follows three forms as shown in Fig. 5.40. The Right and Left midpoint signals, DAC1RM and DAC1LM, produce the average values of the Right and Left channel analog outputs when the enable signals, RMVOEN and LMVOEN are set, respectively. The Right and the Left positive outputs, DAC1RP and DAC1LP, produce 16-bit data in analog form between positive high (VDAC High) and positive low (VDAC Low) boundaries as shown in Fig. 5.40. Similarly, the Right and the Left negative outputs, DAC1RN and DAC1LN, produce an inverted analog signal between negative high (VDAC High) and negative low (VDAC Low) boundaries as shown in the same figure.
5.8
Digital-to-Analog Converter in dspic33fj128mc804
155
DAC1RDAT (or DAC1LDAT) 0xFFFF
0x0000
t
Positive DAC output (DAC1RP or DAC1LP) VDAC High VDAC Mid VDAC Low t VDAC Low VDAC Mid VDAC High Negative DAC output (DAC1RN or DAC1LN)
Fig. 5.40 Dual channel DAC waveforms
The DAC1 clock is an important unit for conversion because it dictates the sampling rate. Both the right and left channels of DAC1 need a time interval equal to TSRATE = 256 TDAC (256 clock periods) to be able to perform digital-to-analog conversion as shown in Fig. 5.41. Once TDAC is determined for DAC1, the sampling rate, FSRATE, simply becomes FSRATE = 1/TSRATE. TSRATE
TDAC 256 TDAC
Fig. 5.41 Definition of sampling time interval, TSRATE, and DAC clock period, TDAC
156
5
Data Converters
The overall oscillator circuit in Fig. 1.3 shows how to generate the DAC clock for dspic33fj128mc804. According to this figure, DAC1 clock can be generated in three ways: the first path originates from the primary oscillator, goes through the S3 port of the PLL MUX, multiplied in the PLL unit, then directed to the port 0 of the auxiliary MUX to become FOUT. This clock signal is then divided by a constant A in the auxiliary divider unit, and subsequently sent to DAC1. The second path originates from the auxiliary oscillator, goes through two 2-1 MUXes in series, and becomes FOUT. This signal is again divided by A, and routed to DAC1. The third path originates from the FRC oscillator whose frequency is divided in the FRCDIV unit. The FRCDIVN output is then directed through the S1 port of the PLL MUX, and then multiplied in the PLL to become PLLOUT. This signal first goes through the 2-1 MUX to become FOUT, then divided by A, and finally sent to the DAC1 unit. Figure 5.42 shows the details of this third clock path. After FIN forms, the first divider in the PLL unit divides FIN by N1 and produces FIN/N1. The Voltage Controlled Oscillator (VCO) multiplies FIN/N1 by M, and produces a much higher frequency, FVCO. However, the secondary divider unit in the PLL divides FVCO by N2, and produces PLLOUT. This output then goes through a 2-1 MUX, and divided by A to become FA. This is the signal forwarded to DAC1. Inside the DAC1 unit, FA is again divided by a second constant B by adjusting DACDIV[6:0] bits in the DAC1 control register, and it becomes the clock frequency for DAC1, FDAC. The inverse of FDAC generates TDAC = 1/FDAC or the clock period for DAC1. FRCDIV[2:0] FRCDIVN = FRC
N
FRC N
S1
FIN
PLLPRE[4:0]
N1
FIN N
FVCO = M
FIN N1
PLLPOST[1:0] PLLOUT = M N2
VCO
FIN N1 N2 APSTSCLR[2:0]
S3
FA = NOSC[2:0] POSCCLK
PLLDIV[8:0]
Phase Lock Loop
M
POSCCLK
AUXCLK
0
0
FOUT
A
FOUT DACFDIV[6:0] A B
FDAC =
FOUT AB
1
SELACLK
1
ASRCSEL
Fig. 5.42 DAC1 clock path
Example 1 Suppose we want to achieve a sampling rate of FSRATE = 8 KHz from DAC1 using the internal FRC oscillator. This is a reasonable rate, producing one analog sample in every 1/8000sec between 5 Hz and 8 KHz audible sound range. Since TSRATE= 256 TDAC, DAC1 clock frequency, FDAC, becomes: FDAC = 256 FSRATE = 256 × 8 KHz = 2.048 MHz. If we choose the internal FRC oscillator, oscillating at the center frequency, 7.37 MHz, we need to activate the S1 port of the 2-1 MUXes on the clock path in Fig. 1.3 in order to generate the DAC1 clock.
5.8
Digital-to-Analog Converter in dspic33fj128mc804
157
Then, from Fig. 5.42: If we let N = 1 (FRCDIV = 0) then FIN = FRC/N = 7.37 MHz. Furthermore, if we let N1 = 2 (PLLPRE = 0) then FIN/N1 = 7.37/2 = 3.685 MHz, which is within the range of 0.8 to 8 MHz according to the datasheet. Assuming M = 40 (PLLDIV =38), FVCO becomes FVCO = M (FIN/N1) = 40 × 3.685 = 147.40 MHz, which is within the allowable range of 100 MHz to 200 MHz. Finally, if N2 = 8 (PLLPOST = 3) then PLLOUT = FVCO/N2 = FOUT = 147.40/ 8 = 18.42 MHz, which is also within 12.5 to 80 MHz. Following the DAC clock path, and assuming A = 1 (APSTSCLR = 7), FA becomes FA = FOUT/A = 18.42 MHz. On the same path, if we let B = 9 (DACFDIV = 8) then FDAC becomes FDAC = FA/B = 18.42/9 = 2.04 MHz, which is very close to the original rate of 2.048 MHz. The path that generates FOSC yields PLLOUT = FOSC = 18.42 MHz and FP = 18.42/2 = 9.21 MHz since the S1 port is active. By choosing DOZE = 0, we obtain FCY = FP/DOZE = 9.21/1 = 9.21 MHz. Example 2 Suppose we want to have a sampling rate of FSRATE = 10 KHz using the XT crystal oscillator, oscillating at 8 MHz. This setup requires booting the processor with the internal oscillator, and then switching it over to the external XT oscillator. Therefore, #pragma statements configuring the oscillator type have to be changed to enable the clock switch-over mechanism. TSRATE= 256 TDAC still requires FDAC = 256 FSRATE = 256 × 10 KHz = 2.56 MHz. In order to engage the XT oscillator, this time we need to activate the S3 port of the 2-1 MUXes on the clock path in Fig. 1.3 to generate the DAC1 clock. Again, from Fig. 5.42: POSCCLK = 8 MHz due to the XT oscillator. If we let N1 = 2 (PLLPRE = 0) then FIN/N1 becomes FIN/N1 = 8/2 = 4 MHz, which is within the range of 0.8 to 8 MHz according to the datasheet. Furthermore, assigning M = 32 (PLLDIV =30) yields FVCO = M (FIN/N1) = 32 × 4 = 128 MHz, which is also within the allowable range of 100 MHz to 200 MHz. Then, if N2 = 2 (PLLPOST = 0) PLLOUT becomes PLLOUT = FVCO/ N2 = FOUT = 128/2 = 64 MHz, which is within 12.5 MHz to 80 MHz. On the DAC1 clock path, choosing A = 1 (APSTSCLR = 7) yields FA = FOUT/ A = 64 MHz. Assuming B = 25 (DACFDIV = 24), FDAC becomes FDAC = FA/ B = 64/25 = 2.56 MHz, which is exactly equal to the original rate. The path that generates FOSC originates from PLLOUT = FOSC = 64 MHz and FP = 64/2 = 32 MHz (as the peripheral clock) since the S3 port is active. Also, by choosing DOZE = 0, we obtain FCY = FP/DOZE = 32/1 = 32 MHz for the CPU. To be able to reconstruct an analog signal at the output of the DAC1 unit, the sampling rate of the ADC, which is responsible for sampling the original analog signal, should be at least double the sampling rate of the DAC1 according to the Nyquist criterion. Therefore, if we want to achieve a sampling rate of 8 KHz to reconstruct an analog signal at the output of DAC1, the ADC sampling rate on the original analog signal must be at least 2 × 8 = 16 KHz.
158
5
Data Converters
To give an example, assume that the Digilent MEMS microphone module, pmodMIC3, is the device that samples sound signals. This module contains an Analog Devices 12-bit serial ADC, ADCS7476. The processing time for this ADC is 16 SPI clock periods in addition to the “quite time” according to the ADCS7476 datasheet. Therefore, TADC = 16 x TSCK + TQUITE = 16 × 50nsec + 50nsec = 850nsec, where TSCK = TQUITE = 50nsec. However, FSRATE = 16 KHz produces TSRATE = 62.5 μsec between samples, which is much larger than TADC = 0.85 μsec. This is the ideal case, and requires 20 MHz SPI clock frequency. However, if this design (MEMS microphone from Digilent and DAC from dspic33fj128mc804) is implemented on a breadboard, the maximum workable SPI clock frequency is about 50 KHz due to RC parasitics in jumper wires and open metal connections inside the breadboard. With this frequency, TSCK becomes 20 μsec. Therefore, reconstruction of one analog sample requires 16 × 20 μsec = 320 μsec, and 30 to 50 μsec quite time to sustain an ADC sampling rate between 2.5 KHz and 2.8 KHz! The control register in DAC1 is the DAC1CON register in Fig. 5.43. In this register, the DAC enable bit, DACEN, enables DAC1 when set. The DAC Stop in Idle bit, DACSIDL, discontinues module operation when the device enters the idle mode. The enable Amplifier bit, AMPON, enables the output amplifier in sleep or idle modes. The FORM bit selects the analog data format at the DAC1 output: FORM = 1 produces an analog value for a signed integer at the DAC1 input; FORM = 0 produces an analog output for an unsigned integer. The DAC1 clock divider bits, DACFDIV[6:0], divides the input clock anywhere between by one to 128 as shown in Table 5.15. 15
14
DACEN
-
13
12
DACSIDL AMPON
11 10 -
-
9
8
7
-
FORM
-
6
5
4
3
2
1
DACFDIV[6:0]
Fig. 5.43 DAC1CON register Table 5.15 DACFDIV[6:0] entry in DAC1CON register DACFDIV[6:0] entry in DAC1CON register 1111111 = Divide clock by 128 ... 0000101 = Divide clock by 6 (default) … 0000010 = Divide clock by 2 0000001 = Divide clock by 1 0000000 = No divide
0
5.8
Digital-to-Analog Converter in dspic33fj128mc804
159
The DAC1 status register, DAC1STAT, has five entries, one for each of the Right and the Left channels as shown in Fig. 5.44. The Left channel Output Enable bit, LOEN, enables the left channel and makes the amplifier operational. Its counterpart, ROEN, enables the right channel and the output amplifier. The Left channel Middle Voltage Output Enable bit, LMVOEN, enables the left channel to produce an average output, DAC1LM. Similarly, its Right channel equivalent, RMVOEN, enables the DAC1RM port. When set, the Left and the Right channel Interrupt bits, LITYPE and RITYPE, generate interrupts when the corresponding data buffer becomes empty. The Left and the Right buffer Full bits, LFULL and RFULL, indicate if the left or the right buffer is full, respectively. In a similar fashion, the Left and Right buffer Empty bits, LEMPTY and REMPTY, show if the left or the right buffer is out of data. 15
14
13
LOEN
-
LMVOEN
12 11 -
-
10
9
8
7
6
5
4
3
2
1
0
LITYPE
LFULL
LEMPTY
ROEN
-
RMVOEN
-
-
RITYPE
RFULL
REMPTY
Fig. 5.44 DAC1STAT register
One of the four registers in the Left and Right data buffers, DAC1LDAT and DAC1RDAT, is shown in Fig. 5.45. Each register is 16 bits wide and stores either a signed or unsigned integer to be processed by DAC1. Fig. 5.45 DAC1LDAT and DAC1RDAT data registers
15 14 13 12 11 10
9
8
7
6
5
4
3
2
1
0
5
4
3
2
1
0
DAC1LDAT[15:0]
15 14 13 12 11 10
9
8
7
6
DAC1RDAT[15:0]
Program 5.3 describes the operation of DAC1 based on the status register. The initialization sequence for the oscillator and the DAC1 unit, the clock path and the I/O setup are accompanied by a simple application program that retrieves random data from the data memory and processes it through the DAC1 unit.
160
5
Data Converters
Program 5.3 // dspic33fj128mc804 DAC operation with status register #include // Configuration Register - FOSCSEL #pragma config IESO = 0 // start with a user-selected oscillator at reset #pragma config FNOSC = 1 // select FRC oscillator with PLL at reset // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled // Configure In-Circuit Programmer, WDT #pragma config ICS = 3 // Program through PGEC1/PGED1 ports #pragma config FWDTEN = 0 // WDT is disabled #define PPSUnLock #define PPSLock
__builtin_write_OSCCONL(OSCCON & 0xBF) __builtin_write_OSCCONL(OSCCON | 0x40)
void main () { unsigned int i; unsigned int Word[8]; // OSCTUN Register OSCTUNbits.TUN = 0;
// select FRC = 7.37MHz
// CLKDIV Register CLKDIVbits.FRCDIV = 0; CLKDIVbits.PLLPRE = 0; CLKDIVbits.PLLPOST = 3; CLKDIVbits.DOZE = 0; CLKDIVbits.DOZEN = 1;
// FRCDIVN = FRC/1 = 7.37MHz = FIN // FIN/2 = 7.37/2 = 3.685MHz // FRCPLL = 147.4/8 = 18.42MHz = FOSC, FP = 18.42/2 = 9.21MHz // FCY = FP/1 = 9.21MHz // DOZE defines the ratio between cpu and peripheral clocks
// PLLFBD Register - PLL feedback divisor register PLLFBDbits.PLLDIV = 38; // FVCO = 3.68x40 = 147.4MHz // ACLKCON Register - Auxiliary clock control for DAC ACLKCONbits.SELACLK = 0; // Select PLL output ACLKCONbits.APSTSCLR = 7; // Auxiliary post-scalar to get FA = FOSC/1 = 18.42MHz ACLKCONbits.AOSCMD = 0; // Auxiliary clock is disabled // Peripheral Pin Select with RP pins PPSUnLock; RPOR4bits.RP8R = 8; // RP8 is for SCK1 (output)) RPINR20bits.SCK1R = 8; // RP8 is for SCK1 (input)) RPINR20bits.SDI1R = 9; // RP9 is for SDI1 RPOR3bits.RP6R = 0; // RP6 is a default pin to be driven manually to generate SS1 RPOR6bits.RP12R = 0; // RP12 is a default pin for DAC1RP PPSLock;
5.8
Digital-to-Analog Converter in dspic33fj128mc804
161
// Define digital I/O TRISBbits.TRISB6 = 0;
// Configure RB6 as an output for SS
TRISBbits.TRISB8 = 0;
// Configure RB8 as an output for SCK1
TRISBbits.TRISB9 = 1;
// Configure RB9 as an input for SDI1
TRISBbits.TRISB12 = 0; // Configure RB12 as an output for DAC1RP // Store data into memory Word[0] = 0x1000; Word[1] = 0xF000; Word[2] = 0x2000; Word[3] = 0xE000; // Configure DAC DAC1STAT = 0; // Reset status register DAC1CONbits.DACFDIV = 28; // FDAC = FA/B = 18.42/29 = 635KHz DAC1CONbits.FORM = 0; DAC1STATbits.ROEN = 1;
// equivalent to DAC sampling rate of 2.5KHz // Data format input for DAC1 is unsigned integer // Enable right positive channel
DAC1CONbits.DACEN = 1; // Enable DAC // Playback the data stored in memory using DAC i = 0; while (1) { if (DAC1STATbits.RFULL != 1) { DAC1RDAT = Word[i];
// if right buffer is not full store more data
i = i + 1; if (i >= 4) i = 0; } } }
Because the program intends to use the internal FRC oscillator, all commands regarding the two-step clock switch-over mechanism are eliminated as shown in Fig. 5.46. These include the statements, #pragma config IESO = 0, to disable any internal-to-external oscillator switch-over mechanism, #pragma config FCKSM = 3, to turn off the clock switching and fail-safe modes, #pragma config OSCIOFNC = 0, to transform the OSCO pin into a general-purpose I/O pin, and #pragma config POSCMD = 3, to disable all primary HS, XT or EC-type oscillators. In addition to the oscillator setup commands, the statement, #pragma config ICS = 3, enables the program clock and data ports, PGEC1 and PGED1, to program dspic33fj128mc804 Plug-In Module (PIM), using PICKIT3 programming platform.
162
5
Data Converters
This program uses the FRC clock and the PLL to generate the CPU, peripheral and DAC1 clocks. Therefore, the #pragma config FNOSC = 1 statement produces the clock internally, and routes through the PLL unit. The OSCTUNbits. TUN = 0 statement defines the FRC clock frequency at 7.37 MHz. The CLKDIVbits.FRCDIV = 0 statement divides the FRC clock frequency by one, and generates 7.37 MHz at FIN. #include // Configuration Register - FOSCSEL // start with a user-selected oscillator at reset #pragma config IESO = 0 #pragma config FNOSC = 1 // select FRC oscillator with PLL at reset // Configuration Register - FOSC // both clock switching and fail-safe modes are disabled #pragma config FCKSM = 3 #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled // Configure In-Circuit Programmer, WDT #pragma config ICS = 3 // Program through PGEC1/PGED1 ports #pragma config FWDTEN = 0 // WDT is disabled … void main () { ... // OSCTUN Register OSCTUNbits.TUN = 0; // CLKDIV Register CLKDIVbits.FRCDIV = 0; CLKDIVbits.PLLPRE = 0; CLKDIVbits.PLLPOST = 3; CLKDIVbits.DOZE = 0; CLKDIVbits.DOZEN = 1;
// select FRC = 7.37MHz
// FRCDIVN = FRC/1 = 7.37MHz = FIN // FIN/2 = 7.37/2 = 3.685MHz // FRCPLL = 147.4/8 = 18.42MHz = FOSC and FP = 18.42/2 = 9.21MHz // FCY = FP/1 = 9.21MHz // DOZE defines the ratio between cpu and peripheral clocks
// PLLFBD Register - PLL feedback divisor register PLLFBDbits.PLLDIV = 38; // FVCO = 3.68x40 = 147.4MHz // ACLKCON Register - Auxiliary clock control for DAC ACLKCONbits.SELACLK = 0; // Select PLL output ACLKCONbits.APSTSCLR = 7; // Auxiliary post-scalar to get FA = FOSC/1 = 18.42MHz ACLKCONbits.AOSCMD = 0; // Auxiliary clock is disabled …
Fig. 5.46 Oscillator setup for dspic33fj128mc804 DAC1
5.8
Digital-to-Analog Converter in dspic33fj128mc804
163
The CLKDIVbits.PLLPRE = 0 statement divides FIN by two, and generates 3.685 MHz before propagating it to the input of the Voltage Controlled Oscillator (VCO) in the PLL. The PLLFBDbits.PLLDIV = 38 statement multiplies FIN/2 by M = 40 to obtain 147.4 MHz at the FVCO port. The CLKDIVbits.PLLPOST = 3 statement further divides 147.4 MHz by eight, and uses it as FOSC = 18.42 MHz and FP = 18.42/2 = 9.21 MHz clocks. Finally, the CLKDIVbits.DOZE = 0 statement divides FP by one, and forms FCY = 18.42 MHz. The statements regarding the auxiliary clock control activate the DAC1 clock path in Fig. 5.42. The ACLKCONbits.AOSCMD = 0 statement disables the auxiliary port, and accepts the clock path from the PLL output. The ACLKCONbits. SELACLK = 0 statement selects the port 0 of the 2-1 MUX, and routes PLLOUT to FOUT. The ACLKCONbits.APSTSCLR = 7 statement divides FOUT = 18.42 MHz by one, delivering 18.42 MHz to FA. // dspic33fj128mc804 DAC operation with status register #include … #define PPSUnLock #define PPSLock
__builtin_write_OSCCONL(OSCCON & 0xBF) __builtin_write_OSCCONL(OSCCON | 0x40)
void main () { … // Peripheral Pin Select with RP pins PPSUnLock; RPOR4bits.RP8R = 8; // RP8 is for SCK1 (output)) RPINR20bits.SCK1R = 8; // RP8 is for SCK1 (input)) RPINR20bits.SDI1R = 9; // RP9 is for SDI1 // RP6 is a default pin to be driven manually to generate SS1 RPOR3bits.RP6R = 0; RPOR6bits.RP12R = 0; // RP12 is a default pin for DAC1RP PPSLock; // Define digital I/O TRISBbits.TRISB6 = 0; TRISBbits.TRISB8 = 0; TRISBbits.TRISB9 = 1; TRISBbits.TRISB12 = 0;
// Configure RB6 as an output for SS // Configure RB8 as an output for SCK1 // Configure RB9 as an input for SDI1 // Configure RB12 as an output for DAC1RP
…
Fig. 5.47 dspic33fj128mc804 SPI1 I/O setup
164
5
Data Converters
The Peripheral Pin Select (PPS) system is engaged by the compiler directives, #define PPSUnlock and #define PPSLock in Fig. 5.47. After the PPS feature is unlocked, the RPOR4bits.RP8R = 8 and RPINR20bits.SCK1R = 8 statements configure the SCK1 to be a bi-directional port for the SPI clock as recommended by Microchip. The statement, RPINR20bits.SDI1R = 9, assigns RP9 to be the SDI port for the SPI1 interface. The statement, RPOR3bits.RP6R = 0, assigns RP6 to be a default pin to be driven manually by the values in the user program in order to generate the active-low slave select bit, SS1. Finally, the statement, RPOR6bits. RP12R = 0, assigns RP12 to be the right positive channel output for DAC1. The statements, TRISBbits.TRISB6 = 0, TRISBbits.TRISB8 = 0, TRISBbits. TRISB12 = 0 and TRISBbits.TRISB9 = 1, configure RB6 (or RP6), RB8 (or RP8), RB12 (RP12) to be outputs, and RB9 (or RP9) to be an input, respectively. Furthermore, the DAC1 unit also needs to be configured as shown in Fig. 5.48. The statement, DAC1CONbits.DACFDIV = 28, divides FA by B = 29, and generates FDAC = 635 KHz according to Fig. 5.42. When divided by 256, this DAC1 frequency can support a sampling rate of 2.5 KHz. The DAC1CONbits. FORM = 0 statement accepts 16-bit DAC1 inputs as unsigned integers. The DAC1STAT = 0 statement initializes the DAC1 status register. The DAC1STATbits.ROEN = 1 statement enables the right output channel. Finally, the DAC1CONbits.DACEN = 1 statement enables the entire DAC1 unit to generate outputs. The application program following the DAC1 initialization step in Fig. 5.48 first stores four randomly selected 16-bit data in an array, Word[i], in the data memory, and then produces an analog output from the DAC1RP terminal for each data packet. The DAC1STATbits.RFULL bit in the status register monitors the data level in DAC1’s right data buffer, DAC1RDAT. As long as this buffer is not full, the program keeps incrementing the index, i, and stores each Word[i] into DAC1RDAT. When the index becomes four, the entire process is reset by the if-statement, if (i >= 4) i = 0, and the data buffer fetches the next four data packets, Word[0] to Word[3], from the data memory.
5.8
Digital-to-Analog Converter in dspic33fj128mc804
165
// dspic33fj128mc804 DAC operation with status register #include … void main () { unsigned int i; unsigned int Word[8]; … // Store data into memory Word[0] = 0x1000; Word[1] = 0xF000; Word[2] = 0x2000; Word[3] = 0xE000; // Configure DAC DAC1STAT = 0; // Reset status register DAC1CONbits.DACFDIV = 28; // FDAC = FA/B = 18.42/29 = 635KHz // equivalent to DAC sampling rate of 2.5KHz DAC1CONbits.FORM = 0; // Data format input for DAC1 is unsigned integer DAC1STATb its.ROEN = 1; // Enable right positive channel DAC1CONbits.DACEN = 1; // Enable DAC // Playback the data stored in memory using DAC i = 0; while (1) { if (DAC1STATbits.RFULL != 1) { DAC1RDAT = Word[i]; // if right buffer is not full store more data i = i + 1; if (i >= 4) i = 0; } } }
Fig. 5.48 An application program to operate the DAC1 unit
Program 5.4 describes the operation of DAC1 based on an Interrupt Service Routine (ISR), DAC1RInterrupt. The initialization sequences for the oscillator and the DAC1 unit, the clock path and the I/O setups are still the same as in Program 5.3.
166
5 Data Converters
A segment of this program continuously applies two random 16 bits of data, 0xFFFF and 0x1111, to the DAC1 unit every time the interrupt occurs. Program 5.4 // dspic33fj128mc804 DAC operation with interrupt #include // Configuration Register - FOSCSEL #pragma config IESO = 0 // start with a user-selected oscillator at reset #pragma config FNOSC = 1 // select FRC oscillator with PLL at reset // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3
// primary oscillators are disabled
// Configure In-Circuit Programmer, WDT #pragma config ICS = 3 // Program through PGEC1/PGED1 ports #pragma config FWDTEN = 0 // WDT is disabled #define PPSUnLock
__builtin_write_OSCCONL(OSCCON & 0xBF)
#define PPSLock
__builtin_write_OSCCONL(OSCCON | 0x40)
unsigned int Delay; volatile unsigned int i; void __attribute__((interrupt, no_auto_psv))_DAC1RInterrupt(void) { IFS4bits.DAC1RIF = 0; if (i == 0)
//Clear Right Channel Interrupt Flag
{ i = 1; DAC1RDAT = 0xFFFF; } else { i = 0; DAC1RDAT = 0x1111; } for (Delay = 0; Delay < 10; Delay++); } void main () {
5.8
Digital-to-Analog Converter in dspic33fj128mc804
// OSCTUN Register OSCTUNbits.TUN = 0;
167
// select FRC = 7.37MHz
// CLKDIV Register CLKDIVbits.FRCDIV = 0;
// FRCDIVN = FRC/1 = 7.37MHz = FIN
CLKDIVbits.PLLPRE = 0; // FIN/2 = 7.37/2 = 3.685MHz CLKDIVbits.PLLPOST = 3; // FRCPLL = 147.4/8 = 18.42MHz = FOSC and FP = 18.42/2 = 9.21MHz CLKDIVbits.DOZE = 0; CLKDIVbits.DOZEN = 1;
// FCY = FP/1 = 9.21MHz // DOZE defines the ratio between cpu and peripheral clocks
// PLLFBD Register - PLL feedback divisor register PLLFBDbits.PLLDIV = 38;
// FVCO = 3.68x40 = 147.4MHz
// ACLKCON Register - Auxiliary clock control for DAC ACLKCONbits.SELACLK = 0; // Select PLL output ACLKCONbits.APSTSCLR = 7; // Auxiliary post-scalar to get FA = FOSC/1 = 18.42MHz ACLKCONbits.AOSCMD = 0;
// Auxiliary clock is disabled
// Peripheral Pin Select with RP pins PPSUnLock; RPOR4bits.RP8R = 8;
// RP8 is for SCK1 (output))
RPINR20bits.SCK1R = 8; // RP8 is for SCK1 (input)) RPINR20bits.SDI1R = 9; // RP9 is for SDI1 RPOR3bits.RP6R = 0; RPOR6bits.RP12R = 0;
// RP6 is a default pin to be driven manually to generate SS1 // RP12 is a default pin for DAC1RP
PPSLock; // Define digital I/O TRISBbits.TRISB6 = 0; TRISBbits.TRISB8 = 0;
// Configure RB6 as an output for SS // Configure RB8 as an output for SCK1
TRISBbits.TRISB9 = 1; TRISBbits.TRISB12 = 0;
// Configure RB9 as an input for SDI1 // Configure RB12 as an output for DAC1RP
// Configure DAC i = 0; DAC1STAT = 0; // Reset status register DAC1CONbits.DACFDIV = 28; // FDAC = FA/B = 18.42/29 = 635KHz DAC1CONbits.FORM = 0;
// equivalent to DAC sampling rate of 2.5KHz // Data format input for DAC1 is unsigned integer
IFS4bits.DAC1RIF = 0; IEC4bits.DAC1RIE = 1;
// Clear right channel interrupt flag // Enable right channel interrupt flag
DAC1STATbits.ROEN = 1;
// Enable right positive channel
DAC1CONbits.DACEN = 1; // Enable DAC // Test DAC output with interrupts while (1); }
168
5
Data Converters
Fig. 5.49 describes the ISR, DAC1RInterrupt, and the application program that uses this ISR. // dspic33fj128mc804 DAC operation with interrupt #include … unsigned int Delay; volatile unsigned int i; void __attribute__((interrupt, no_auto_psv))_DAC1RInterrupt(void) { IFS4bits.DAC1RIF = 0; // Clear Right Channel Interrupt Flag if (i == 0) { i = 1; DAC1RDAT = 0xFFFF; } else { i = 0; DAC1RDAT = 0x1111; } for (Delay = 0; Delay < 10; Delay++); } void main () { … // Configure DAC i = 0; // Reset status register DAC1STAT = 0; DAC1CONbits.DACFDIV = 28; // FDAC = FA/B = 18.42/29 = 635KHz // equivalent to DAC sampling rate of 2.5KHz DAC1CONbits.FORM = 0; // Data format input for DAC1 is unsigned integer IFS4bits.DAC1RIF = 0; // Clear right channel interrupt flag IEC4bits.DAC1RIE = 1; // Enable right channel interrupt flag DAC1STATbits.ROEN = 1; // Enable right positive channel DAC1CONbits.DACEN = 1; // Enable DAC // Test DAC output with interrupts while (1); }
Fig. 5.49 Interrupt Service Routine setup and DAC1 operation
5.8
Digital-to-Analog Converter in dspic33fj128mc804
169
Initially, the index, i, is set to zero while the DAC1 unit is initialized and enabled. Since only the right channel of DAC1 is used, the interrupt flag for this channel, IFS4bits.DAC1RIF, is first cleared, and then enabled by the statement, IEC4bits. DAC1RIE = 1. When the right channel data buffer, DAC1RDAT, becomes full of data the interrupt triggers, and IFS4bits.DAC1RIF = 1. The program subsequently clears the interrupt by IFS4bits.DAC1RIF = 0, and goes to the if (i == 0) statement to assign i = 1, and DAC1RDAT = 0xFFFF. When the DAC1RIF fires the second time, the program goes into the else-statement, and assigns i = 1, and DAC1RDAT = 0x1111 since the interrupt is still enabled. The ping-pong motion of the analog signal, corresponding to 0xFFFF and 0x1111 values, can be seen from the DAC1RP terminal (RB12 pin) on the oscilloscope. Projects 1. Write a program for the ADC unit in dspic33fj128mc802 such that an analog signal at AN0 is sampled continuously in auto sampling mode as shown in Program 5.2. First, apply a series of constant analog voltages at AN0 and check each 16-bit digital value generated by the ADC in the Watch window by inserting breakpoints in the program. Once this step is successfully accomplished, then apply an analog waveform, such as sinusoidal or a triangular signal, at AN0. Store the digital data in the data memory. Compare what is stored against the expected values. 2. Perform the same task with the analog waveform applied at AN0 in question 1, but sample the waveform at designated time intervals such as once every 1 μsec (or any other time of your choice). Store the sampled 16-bit data in data memory, and compare it against expected values. 3. Write a program for the ADC unit in dspic33fj128mc802 such that six different constant analog signals applied to analog ports AN0 to AN5 are sampled sequentially but continuously in auto sampling mode. Use breakpoints and the Watch window to check the data accuracy. 4. Use the program in question 3, but sample between designated time intervals. Store the data, and inspect them for accuracy. 5. Use the set up in question 1, but change the output format from unsigned integer to signed integer, signed fraction, and finally unsigned fraction. Compare the 16 bit output data from ADC for data consistency and accuracy. 6. Implement the IR detector experiment in Fig. 5.35. Rotate the target and check at which point the detector is not able to receive correct data to measure distances. 7. Construct a 10-bit DAC with weighted binary adder scheme in Fig. 5.38. Write a program to convert constant analog signals at AN0, store the data, and reconstruct it, using the 10-bit DAC. This experiment requires ten digital ports configured as outputs.
170
5
Data Converters
References 1. dspic33fj128mc802/804 Microchip processor datasheet, https://www.microchip.com/en-us/ product/dsPIC33FJ128MC802, Accessed January 2023. 2. MPLABX software design environment, https://www.microchip.com/en-us/tools-resources/ develop/mplab-x-ide, Accessed January 2023. 3. Sharp Inc. GP2Y0A02 distance/proximity sensor, Sharp Inc. GP2Y0A02 distance/proximity sensor, Accessed January 2023.
Chapter 6
Peripherals
6.1
Accelerometer
Accelerometer is a device that measures angles. This is a vital device for a mobile robot because it maintains its stability when configured properly. When the device is tilted in any direction from a horizontal plane defined by x and y axes, or from tilted from the vertical z axis, it generates all rotational angles at its outputs. We demonstrate the basic features of a three-axis MEMS accelerometer in this section using ADXL362 from Analog Devices Corporation [1]. The equations below represent the pitch (the rotation in x-axis), the roll (the rotation in y-axis) and yaw (the rotation in z-axis) angles of an accelerometer. The absolute magnitude of the cross product between two arbitrary vectors is given by: →
→
→
→
j a × b j = j a j j b j sin Φ
ð6:1Þ
Similarly, the absolute magnitude of the dot product between two vectors is given by: →
→
→
→
j a • b j = j a j j b j cos Φ →
ð6:2Þ →
Here, Φ corresponds the angle between the vectors a and b : → → → If the gravity vector, g , in Fig. 6.1 is written in terms of its unitary vectors, i , j → → and k , then g can be expressed in terms of GX, GY AND GZ as follows: →
→
→
→
g = i :GX þ j :GY þ k :GZ
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 A. Bindal, Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers, https://doi.org/10.1007/978-3-031-27841-9_6
ð6:3Þ
171
172
6
Peripherals
Fig. 6.1 Definition of roll and pitch angles with respect to x and y-axis →
→
The cross product between g with i yields: →
→
→
→
→
→
→
→
j g × i j = = j i GX þ j GY þ k GZ × i j = - k GY þ j GZ =
ð6:4Þ
GY 2 þ GZ 2
Applying Eq. 6.1 to Eq. 6.4 results: →
→
→
→
→
→
j g × i j = j g j j i j sin Φ = j g j sin ð90- ΘX Þ = j g j cos ΘX
ð6:5Þ
Thus, GY 2 þ GZ 2 → jgj
cos ΘX = →
ð6:6Þ
→
Similarly, the dot product between g with i yields: →
→
j g • i j = GX
ð6:7Þ
Applying Eq. 6.2 to Eq. 6.7 produces: →
→
→
→
→
→
j g • i j = j g j j i j cos Φ = j g j cos ð90- ΘX Þ = j g j sin ΘX
ð6:8Þ
6.1
Accelerometer
173
Thus, sin ΘX =
GX → jgj
ð6:9Þ
Combining sin ΘX and cos ΘX in one equation yields: tan ΘX =
GX
ð6:10Þ
GY 2 þ GZ 2
Therefore, the roll angle (the roll around the x-axis) becomes: ΘX = tan - 1
GX
ð6:11Þ
GY 2 þ GZ 2
Similarly, the pitch angle (the pitch around the y-axis) can similarly be determined as follows: ΘY = tan - 1
GY
ð6:12Þ
GX 2 þ GZ 2
The tilt angle with respect to the z-axis can be calculated from Fig. 6.2.
x
xnew y
ΘZ
ynew
GZ g znew Fig. 6.2 Definition of tilt angle with respect to z-axis
174
6
Peripherals
→
→
Forming the cross product between g with k yields: →
→
→
→
→
→
→
→
j g × k j = = j i GX þ j GY þ k GZ × k j = - j GX þ i GY =
ð6:13Þ
GX 2 þ GY 2
But, →
→
→
→
sin ΘZ =
GX 2 þ GY 2 → jgj
→
j g × k j = j g j j k j sin Φ = j g j sin ΘZ
ð6:14Þ
Thus, ð6:15Þ
→
→
Similarly, the dot product between g with k yields: →
→
j g • k j = GZ
ð6:16Þ
But, →
→
→
→
→
j g • k j = j g j j k j cos Φ = j g j cos ΘZ cos ΘZ =
GZ → jgj
ð6:17Þ ð6:18Þ
Combining sin ΘZand cos ΘZ terms yields: tan ΘZ =
GX 2 þ GY 2 GZ
ð6:19Þ
Thus, ΘZ = tan - 1
GX 2 þ GY 2 GZ
ð6:20Þ
Equations 6.11, 6.12 and 6.20 are the angles produced at the ADXL362 outputs every time the accelerometer is tilted to an arbitrary position. The ADXL362 has two basic modes of operation when it measures angles: continuous mode and wake-up mode.
6.1
Accelerometer
175
The continuous mode is the device’s primary mode of operation where the device produces acceleration data based on the roll, pitch and tilt angles compared to its original position. In this mode, the device can output data at a rate between 12.5 and 400 samples per second (Hz), depending on the application. The wake-up mode consumes extremely low power, and aims for applications when there is continuous inactivity around the device. This mode is particularly useful for applications such as motion activated on/off switch when the rest of the circuit is powered down until an activity is detected. The device measures the presence of activity at a rate of 6 samples per second (Hz) in this mode. When it detects activity, the accelerometer can be switched to the continuous mode of operation with an increased bandwidth up to 400 Hz, or it can trigger an interrupt to dspic33fj128mc802. The device has also a standby mode where it does not take any measurements. Motion detection mechanisms in ADXL362 are engaged in two ways: either the device detects activity or inactivity. Either event is initiated based on a user-defined activity or inactivity threshold value, which is based on an absolute or a referenced scale. In absolute scale, if the measured acceleration data is continuously below a userdefined inactivity threshold for a period of time, the device remains in inactive state as shown below. Acceleration < Inactivity Threshold Similarly, if the acceleration data is above the activity threshold for a certain period of time, the device operates in active state as shown below. Acceleration > Activity Threshold For reference scale, the decision is rather based on the difference between acceleration data and an internally generated reference value. Therefore, for the device to remain in inactive state, the following equation has to be satisfied for a time duration specified by the user. (Acceleration – Reference) < Inactivity Threshold However, the device operates in active state as long as the following equation holds in a user-specified time interval if it is configured under referenced scale. (Acceleration – Reference) > Activity Threshold In some applications, it is best if the device is not configured under absolute scale. For example, in steady state condition, the device is exposed to constant gravitational force which is 1g in negative z-direction. When the device is configured in reference mode, the effect of gravity is removed, and the acceleration measurements in z-axis become available for a user application. Both activity and inactivity detection mechanisms can be engaged in a variety of different ways when the device operates under the measurement or wake-up configurations. These are default mode, linked mode, loop mode and auto-sleep mode of operations. In default mode, the user must engage the activity and inactivity functions. When the device enters this mode, both activity and inactivity detection mechanisms
176
6
Peripherals
remain enabled, and all interrupts generated by the device must be serviced by the host processor. The activity and inactivity detection mechanisms are concurrent events, and depicted in the form of two independent state diagrams as shown in Fig. 6.3.
Inactive
WAIT FOR ACTIVITY
Activity detected
ACTIVITY INTERRUPT
AWAKE = 1
Activity interrupt cleared (read SR)
WAIT TO CLEAR INTERRUPT
Wait until CPU clears activity interrupt (T)
Active
WAIT FOR INACTIVITY
Inactivity detected
INACTIVITY INTERRUPT
AWAKE = 1
Inactivity interrupt cleared (read SR)
WAIT TO CLEAR INTERRUPT
Wait until CPU clears inactivity interrupt (T)
Fig. 6.3 Activity and inactivity events in default mode
In the first state diagram, the device is initially inactive and waits for any hint of activity. As long as the acceleration data generated by the device remains below the inactivity threshold, the accelerometer remains in the WAIT FOR ACTIVITY state. However, as soon as acceleration data increases above the activity threshold limit, and remains there for a certain amount of time, the device enters the ACTIVITY INTERRUPT state where it creates an interrupt for the host processor. Clearing the activity interrupt means for the user program to read the contents of the status register in ADXL362. This is dependent on the application in the user program, and may take an arbitrary amount of time, T. During this period the device remains in the WAIT TO CLEAR INTERRUPT state. When the status register is finally read, the accelerometer leaves this state, and goes back to the prior WAIT FOR ACTIVITY state, waiting for any trace of activity. Fig. 6.4 shows an example regarding the first state diagram in Fig. 6.3. In this example, the device is in inactive state, waiting for an activity event. When acceleration data exceeds the activity threshold at t = T1, and remains consistently above
6.1
Accelerometer
177
this limit for a period of time, the activity event is recognized, and an interrupt is generated at t = T2. The application program reads the contents of the status register within a certain period of time, and the interrupt flag is cleared at t = T3. The device goes back to the inactive state, and waits for acceleration data. If new data sustains values above the activity threshold limit within a certain time period, the activity event is recognized again, and the device generates another interrupt at t = T4. This interrupt is cleared at t = T5. When the new data arrives, the cycle repeats once again.
Activity threshold
Activity detected (go to ACTIVITY INTERRUPT state)
Activity detected (go to ACTIVITY INTERRUPT state)
t = T2
t = T4
T
T
t = T3 Read SR – interrupt cleared (go to WAIT FOR ACTIVITY state)
Inactivity threshold
START
t = T1
t = T5
Inactive (Stay in WAIT FOR ACTIVITY state)
Read SR – interrupt cleared (go to WAIT FOR ACTIVITY state)
Fig. 6.4 Example of activity detection in default mode
In the second state diagram, the device is initially in active state, waiting for signs of inactivity. As long as the acceleration data generated by the device remains above the activity threshold, the accelerometer remains in the WAIT FOR INACTIVITY state. However, as soon as acceleration data drops below the inactivity threshold limit, and remains there for a period of time, the device enters the INACTIVITY INTERRUPT state, and creates an interrupt for the host processor. Clearing the inactivity interrupt means reading the contents of the status register, and this may take an arbitrary amount of time as explained previously. During this period the device remains in the WAIT TO CLEAR INTERRUPT state. When the interrupt is cleared, the accelerometer goes back to the initial WAIT FOR INACTIVITY state, and waits for any trace of inactivity. Fig. 6.5 shows an example regarding the second state diagram in Fig. 6.3. In this example, the device is initially in the active state, waiting for an inactivity event. When acceleration data stays below the inactivity threshold, and remains there consistently for a period of time, the inactivity event is recognized at t = T1, and an inactivity interrupt is generated. The application program reads the status register after t = T, and the interrupt flag is cleared at t = T2. The device goes back to its active state, and waits for new acceleration data to arrive. If new data jumps above the inactivity threshold limit for a few times, or even exceeds the activity threshold occasionally, the device still stays in its inactive state because the acceleration data
178
6
Peripherals
does not consistently sustain values above the activity threshold limit for a given time period. The inactivity event is recognized again at t = T3, and another interrupt is generated. This interrupt is cleared at t = T4. When the new data arrives with values exceeding the active threshold the limit, the device still remains in its active state, and no interrupt is generated. Read SR – interrupt cleared (go to WAIT FOR INACTIVITY state) t = T4 Read SR – interrupt cleared (go to WAIT FOR INACTIVITY state) t = T2 Activity threshold
t = T5 Active (stay in WAIT FOR INACTIVITY state)
Inactivity threshold
T
T
START t = T1 Inactivity detected (go to INACTIVITY INTERRUPT state)
t = T3 Inactivity detected (go to INACTIVITY INTERRUPT state)
Fig. 6.5 Example of inactivity detection in default mode
The link mode combines the two separate state diagrams in Fig. 6.3 in a single state diagram as shown in Fig. 6.6. If the device is initially in inactive state, waiting for signs of activity, it will require a new acceleration data to be consistently higher than the activity threshold limit for a certain period of time. When activity is detected, an activity interrupt is generated, and the device becomes awake as shown in the figure. The host processor clears the interrupt, the device enters the active state, and waits for signs of inactivity. When the new acceleration data is consistently below the inactive threshold limit, the inactivity status of the device is recognized, and an inactivity interrupt is generated. Now, the accelerometer is not awake any longer, and waits until the interrupt flag is cleared to enter the inactive state. Here, the device waits for signs of activity, and the cycle repeats once again. Wait until CPU clears activity interrupt (T)
Activity detected Inactive
WAIT FOR ACTIVITY
ACTIVITY INTERRUPT
AWAKE = 1
Activity interrupt cleared (read SR)
Inactivity interrupt cleared (read SR)
WAIT TO CLEAR INTERRUPT
WAIT TO CLEAR INTERRUPT
AWAKE = 0
INACTIVITY INTERRUPT
Wait until CPU clears inactivity interrupt (T)
Fig. 6.6 Activity and inactivity events in linked mode
Inactivity detected
WAIT FOR INACTIVITY
Active
6.1
Accelerometer
179
The third major mode is the loop mode as shown in Fig. 6.7. This mode removes the complexities associated with interrupts. The device flips back and forth between two states according to the collected acceleration data within a given period of time. If the device is initially in the inactive state, it will stay there, waiting for signs of activity. If the new data is consistently below the inactivity threshold, the device remains in the inactive state. However, if the data is above the activity threshold limit, and the activity is detected, the device goes to the active state. Here, the device waits for signs of inactivity. Again, if the next set of data is above the activity threshold, the device stays awake, and remains in the same state. Otherwise, it switches states, and becomes inactive.
Inactive
Activity detected AWAKE = 1
WAIT FOR ACTIVITY
WAIT FOR INACTIVITY
Active
Inactivity detected AWAKE = 0
Fig. 6.7 Activity and inactivity events in loop mode
A good example when the device is in loop mode is given in Fig. 6.8. In this example, the device is initially in the inactive state, sampling acceleration data with an Output Data Rate (ODR) of 6 Hz. This is the normal data rate when the accelerometer is in sleep mode as mentioned previously. When the acceleration data is consistently below the inactivity threshold until t = T, the device stays in the inactive state, still waiting for signs of activity. The second set of data stays above the activity threshold, and makes the device change its current state to the active state at t = 2T. Since the device is now active and awake, the user can increase the ODR from 6 Hz to 100 Hz. The third and fourth sets of data mostly remain above the activity threshold limit with some exceptions in the fourth set as shown in the figure. But, this does not deter the device to depart from its active state either at t = 3T or t = 4T. The fifth set, however, is consistently below the inactivity threshold limit, which moves the device from the active to the inactive state at t = 5T.
Activity threshold
ODR = 6Hz
ODR = 6Hz
ODR = 100Hz
ODR = 100Hz
ODR = 100Hz
Inactivity threshold
START
t=T Inactive
t = 2T Active
t = 3T Active
Fig. 6.8 Example of activity and inactivity detection in loop mode
t = 4T Active
t = 5T Inactive
180
6
Peripherals
The registers in ADXL362 are divided into two categories: data registers and control registers. Data registers hold the x, y and z-axis acceleration data in 12-bit and 8-bit signed integer formats. The 12-bit data registers concatenate two 8-bit registers named high data register and low data register. The most significant four bits in the high data register are the sign extension bits from the most significant data bit located at bit no. 11. In our application with this accelerometer, we only used the lower resolution 8-bit XDATA, YDATA and ZDATA registers in Little Endian format (most significant data bit is placed at bit no. 7, and the least significant bit at bit no. 0) as shown in Fig. 6.9. Fig. 6.9 XDATA, YDATA and ZDATA data registers in ADXL362
7
6
5
4
3
2
1
0
1
0
1
0
Address = 0x08
XDATA[7:0]
7
6
5
4
3
2
Address = 0x09
YDATA[7:0]
7
6
5
4
3
2
Address = 0x0A
ZDATA[7:0]
The Activity/Inactivity control register, ACT_INACT_CTL, that selects between the absolute and reference modes of operation, and defines how to trigger activity and inactivity is shown in Fig. 6.10. The LINK/LOOP[1:0] entry in this register defines if the accelerometer is operated in the default, linked, or loop mode as shown in Tale 6.1. These modes were previously discussed in Figs. 6.3, 6.6 and 6.7, respectively. The Inactivity Reference bit, INACTREF, selects inactivity in the reference mode when set. Otherwise, the absolute mode is selected. The Inactivity Enable bit, INACTEN, simply enables inactivity. Similar to inactivity reference bit, the Activity Reference bit, ACTREF, chooses activity in the reference mode when set. Otherwise, activity starts in the absolute mode. The Activity Enable bit, ACTEN, enables activity. Both INACTEN and ACTEN bits have to be defined when the device is in default mode where inactivity and activity have to be manually initiated. The enable bits are irrelevant for link and loop modes (Table 6.1).
6.1
Accelerometer 7
6
-
-
181 5
4
3
2
1
0
INACTREF
INACTEN
ACTREF
ACTEN
LINK/LOOP[1:0]
Fig. 6.10 Activity/Inactivity control register (address = 0x27)
Table 6.1 ADXL362 modes of operation LINK/LOOP[1:0] entry in ACT/INACT register X0 = Default mode 01 = Linked mode 11 = Loop mode
Our application did not call for FIFO operations in ADXL362, therefore we will not discuss the FIFO control or FIFO samples register in this section. However, the reader interested in managing the accelerometer data using FIFOs is encouraged to read the relevant part from the datasheet. The Interrupt 1 Function Map register, INTMAP1, configures the INT1 interrupt pin as shown in Fig. 6.11. 7
6
5
4
3
2
1
0
INTLOW
AWAKE
INACT
ACT
FIFOOR
FIFOWM
FIFORDY
DATARDY
Fig. 6.11 INT1 Function Map register, INTMAP1 (address = 0x2A)
The INTLOW bit in this register sets the INT1 pin as an active-low pin. The AWAKE bit maps the awake bit in the status register onto the INT1 pin. Similarly, the INACT and ACT bits map the inactivity and activity bits in the status register onto the INT1 pin, respectively. The FIFO Overrun bit, FIFOOR, FIFO Watermark bit, FIFOWM, and FIFO Ready bit, FIFORDY, map the related status bits onto the INT1 pin. Finally, the Data Ready bit, DATARDY, maps the corresponding bit in the status register onto the INT1 pin. An identical register to the INTMAP1 register in Fig. 6.11 exists for the second interrupt pin, INT2. The location and the functionality of each bit (from the INTLOW to the DATARDY bits) in the INT2 Function Map register, INTMAP2, is exactly the same as the ones in the INT1 map register. The address of this register is 0x2B in the memory map. The Filter Control register, FILTER_CTL, selects the acceleration limits, bandwidth and output data rate as shown in Fig. 6.12.
182
6
Fig. 6.12 Filter Control register, FILTER_CTL (address = 0x2C)
7
6
5
4
3
-
HALFBW
EXTSAMPLE
RANGE[1:0]
Peripherals 2
1
0
ODR[2:0]
The measurement range entry, RANGE[1:0], is given in Table 6.2. The maximum default range is +/-2g. However, the range can be increased to +/-8g provided that courser graduations of acceleration data will be acquired by the x, y, z data registers. The Half Bandwidth bit, HALFBW, sets the bandwidth of anti-alias filters to a quarter ODR. Otherwise, the bandwidth is set to half the ODR. The External Sampling Trigger bit, EXTSAMPLE, uses INT2 pin for external control when set. Table 6.2 Measurement range in ADXL362 RANGE[1:0] entry in Filter control register 00 = +/- 2g (default) 01 = +/- 4g 11 = +/- 8g
Finally, the Output Data Rate entry, ODR[2:0], in the FILTER_CTL register sets the data rate according to Table 6.3. Table 6.3 Output Data Rate in ADXL362 ODR[2:0] entry in Filter control register 000 = 12.5 samples/sec 001 = 25 samples/sec 010 = 50 samples/sec 011 = 100 samples/sec (default) 100 = 200 samples/sec 101 = 400 samples/sec 110 = 400 samples/sec 111 = 400 samples/sec
6.1
Accelerometer
183
The Power Control register, POWER_CTL, controls the power dissipation of ADXL362 according to its mode of operation as shown in Fig. 6.13. The External Clock bit, EXTCLK, allows an external clock to be applied to ADXL362 through the INT1 pin. Fig. 6.13 Power Control register, POWER_CTL (address = 0x2D)
7
6
-
EXTCLK
5
4
3
2
WAKEUP
AUTOSLEEP
LOWNOISE[1:0]
1
0
MEASURE[1:0]
The Low Noise entry, LOWNOISE[1:0], either selects normal operation or low noise mode according to Table 6.4. The Auto-sleep bit, AUTOSLEEP, engages auto-sleep mode if the accelerometer operates in linked or loop mode. The Measure entry, MEASURE[1:0], shown in Table 6.5 selects between measurement and standby modes. Table 6.4 Low Noise modes in ADXL362 LOWNOISE[1:0] entry in Power control register 00 = Normal operation (default) 01 = Low noise mode 10 = Ultra low noise mode 11 = Reserved
Table 6.5 Measurement or standby modes in ADXL362 MEASURE[1:0] entry in Power control register 00 = Standby 10 = Measurement mode X1 = Reserved
184
6
Peripherals
The activity and inactivity threshold limits are defined by four independent 8-bit registers in Fig. 6.14. The Activity Threshold register is an 11-bit register concatenated by the least significant three bits of the Activity High Threshold register, THRES_ACT_H, and all eight bits of the Activity Low Threshold register, THRES_ACT_L, in this figure. The most significant bit of the activity threshold value corresponds to bit no. 2 of the THRES_ACT_H register while the least significant bit corresponds to bit no. 0 of the THRES_ACT_L register. Similarly, the 11-bit Inactivity Threshold register is the combination of two 8-bit registers in the same figure. The most significant bit of the inactivity threshold resides at bit no. 2 of the THRES_INACT_H register while the least significant bit resides at bit no. 0 of the THRES_INACT_L register. The time intervals that determine the device activity or inactivity are set by three 8-bit registers as shown in Fig. 6.15. The time interval for activity is defined only by eight bits of the Time Activity register, TIME_ACT. The time interval for inactivity, on the other hand, is a much larger value, and it requires a 16 bit field that combines the TIME_INACT_H and TIME_INACT_L registers. The most significant bit of the time activity corresponds to bit no. 7 of the TIME_INACT_H register while its least significant bit corresponds to bit no. 0 of the TIME_INACT_L register. 7
6
5
4
3
2
1
0
Address = 0x20 Activity Threshold register
THRES ACTL[7:0]
Address = 0x21
7
6
5
4
3
-
-
-
-
-
2
1
0
THRES ACTH[10:8]
7
6
5
4
3
2
1
0
Address = 0x23 Inactivity Threshold register
THRES INACTL[7:0]
Address = 0x24
7
6
5
4
3
-
-
-
-
-
2
1
0
THRES INACTH[10:8]
Fig. 6.14 Activity and Inactivity Threshold registers (address = 0x20, 0x21, 0x23, 0x24)
6.1
Accelerometer
185 7
6
5
4
3
2
1
0 Time Active register
Address = 0x22
TIME ACT[7:0]
7
6
5
4
3
2
1
0
Address = 0x25
Time Inactive register
TIME INACTL[7:0]
7
6
5
4
3
2
1
0
Address = 0x26
TIME INACTH[7:0]
Fig. 6.15 Activity and Inactivity Time registers (address = 0x22, 0x25, 0x26)
The last important register to make decisions in the user application program is the status register shown in Fig. 6.16. The Error bit, ERR, shows ADXL362 has not been configured yet. This bit is initially set at logic 1. However, when the first control register is programmed, the ERR bit becomes logic 0. The Awake bit, AWAKE, shows ADXL362 is awake, and taking measurements in the active or inactive states at a high data rate. The Inactivity bit, INACT, represents inactivity status when set. Similarly, when the device enters the active state, the Activity bit, ACT, is set. The FIFO status bits, FIFOOR, FIFOWM and FIFORDY, show if the FIFO has been overrun with new data (before the old data is read), if the FIFO is at its designated watermark level, and if it is ready to provide data (at least one sample available in FIFO), respectively. The Data Ready bit, DATARDY, indicates acceleration samples are available in XDATA, YDATA and ZDATA registers. This bit clears as soon as data registers (or FIFO) is read.
7
6
5
4
3
2
1
0
ERR
AWAKE
INACT
ACT
FIFOOR
FIFOWM
FIFORDY
DATARDY
Fig. 6.16 Status register (address = 0x0B)
186
6
Peripherals
The communication with ADXL362 is maintained by the SPI bus protocol. As discussed previously in Chapter 3, this protocol uses four ports, SS, SCK, SDI and SDO from dspic33fj128mc802 to communicate with any peripheral with serial interface. The accelerometer has an active-low Chip-Select input, CS, which is equivalent to SS:. The other ports of the accelerometer, SCLK, MOSI and MISO, are also equivalent to the SCK, SDI and SDO ports of the microcontroller respectively. No additional hardware was required to sustain serial communication between the microcontroller and ADXL362. However, the SPI protocol on ADXL was modified as shown in Fig. 6.17. In common SPI protocol, SS serves as a port to select the peripheral, and data is sent to the slave from the SDO port of the microcontroller. Similarly, once a particular slave is selected, the data is received from the slave using the SDI port. However, the accelerometer is not a simple slave device, but it contains many control registers that need to be programmed before any communication takes place. Therefore, the read and write commands to a specific accelerometer register require the register address and data to be sent out simultaneously, using the MOSI port. The MISO port is used only to receive the contents of the status register, or the contents of data registers from the accelerometer. In the first waveform of Fig. 6.17, the microcontroller sends the read command, 0x0B, followed by an 8-bit register address to the MOSI port of ADXL362 at the negative edge of SCLK. The accelerometer reads these entries at the positive edge of SCLK. Similarly, the target register data is received from the MISO port at the negative edge of SCLK, and the microcontroller reads and processes this data at the positive clock edge. The second waveform describes receiving a sequence of 8-bit data packets from ADXL362, most likely from the accelerometer’s FIFO. The third waveform in Fig. 6.17 describes how to write a byte of data to ADXL362. Just like in the read case, the write command and the register address are sent out to the accelerometer through the MOSI port. Then the microcontroller transmits the data byte to be written to the target register. All command, address and data bits are delivered at the negative edge of the clock so that the accelerometer can read them at the positive edge. The fourth waveform describes how to write a multitude of data packets to the accelerometer. Again, they are all delivered at the negative edge of SCLK.
6.1
Accelerometer
187
CS
SCLK
MOSI
0 0 0 0 1 0 1 1 7 6 5 4 3 2 1 0 Read command (0x0B)
MISO
8-bit Address
High impedence
7 6 5 4 3 2 1 0 8-bit Data
CS
SCLK
MOSI
0 0 0 0 1 0 1 1 7 6 5 4 3 2 1 0 Read command (0x0B)
MISO
8-bit Address
High impedence
7 6 5 4 3 2 1 0
7 6 5 4 3 2 1 0
8-bit Data 1
8-bit Data N
CS
SCLK
MOSI
0 0 0 0 1 0 1 1 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 Write command (0x0A)
MISO
8-bit Address
8-bit Data
High impedence
CS
SCLK
MOSI
0 0 0 0 1 0 1 1 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 Write command (0x0A)
MISO
8-bit Address
High impedence
Fig. 6.17 ADXL362 read and write SPI protocols
8-bit Data 1
7 6 5 4 3 2 1 0 8-bit Data N
188
6
Peripherals
Program 6.1 reads data from ADXL362 continuously, using the SPI interface when the accelerometer operates in default mode [2, 3]. Because of its length, the program is divided into several sub-sections. Each sub-section will be described individually. Program 6.1 // The accelerometer continuously reads data (always AWAKE) in default mode using SPI #include "math.h" #include "xc.h" // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled // Configuration Register - FOSCSEL #pragma config IESO = 0
// start with a user-selected oscillator at reset
#pragma config FNOSC = 7
// select FRC oscillator with postscalar at reset
// Configuration Register - FICD #pragma config JTAGEN = 0 //JTAG is disabled #define PPSUnLock
__builtin_write_OSCCONL(OSCCON & 0xBF)
#define PPSLock
__builtin_write_OSCCONL(OSCCON | 0x40)
unsigned char SPI_Transmit (unsigned char TxValue) { while (SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = TxValue;
// When empty, send the byte to the TX buffer
while (SPI1STATbits.SPIRBF == 0); // As valid bits shifts out of SDO, junk bits are received at SDI // Wait until the RX buffer is full of junk data return SPI1BUF; }
// When full, read the junk data in RX buffer through SPI1BUF
unsigned char SPI_Receive () { while (SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = 0x00; // When empty, send the junk byte (0x00) to the TX buffer while (SPI1STATbits.SPIRBF == 0); // As junk bits shifts out of SDO, valid bits are received at SDI // Wait until the RX buffer is full of valid data return SPI1BUF; }
// When full, read the valid data in RX buffer through SPI1BUF
6.1
Accelerometer
189
void Write_Threshold_Activity_LReg (unsigned char DataByte) { LATBbits.LATB6 = 0;
// Lower SS for instruction delivery
SPI_Transmit (0x0A); SPI_Transmit (0x20);
// Send the Write Register instruction // Send the address of Write Threshold Activity Low Register
SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void Write_Threshold_Activity_HReg (unsigned char DataByte) { LATBbits.LATB6 = 0;
// Lower SS for instruction delivery
SPI_Transmit (0x0A); SPI_Transmit (0x21);
// Send the Write Register instruction // Send the address of Write Threshold Activity High Register
SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; }
// Raise SS for instruction completion
void Write_Threshold_Inactivity_LReg (unsigned char DataByte) { LATBbits.LATB6 = 0;
// Lower SS for instruction delivery
SPI_Transmit (0x0A);
// Send the Write Register instruction
SPI_Transmit (0x23); // Send the address of Write Threshold Inactivity Low Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; }
// Raise SS for instruction completion
void Write_Threshold_Inactivity_HReg (unsigned char DataByte) { LATBbits.LATB6 = 0; SPI_Transmit (0x0A);
// Lower SS for instruction delivery // Send the Write Register instruction
SPI_Transmit (0x24); // Send the address of Write Threshold Inactivity High Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; }
// Raise SS for instruction completion
void Write_Time_Activity_Reg (unsigned char DataByte) { LATBbits.LATB6 = 0; SPI_Transmit (0x0A);
// Lower SS for instruction delivery // Send the Write Register instruction
SPI_Transmit (0x22); // Send the address of Time Activity Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; }
// Raise SS for instruction completion
190
6
void Write_Time_Inactivity_LReg (unsigned char DataByte) { LATBbits.LATB6 = 0; SPI_Transmit (0x0A);
// Lower SS for instruction delivery // Send the Write Register instruction
SPI_Transmit (0x25); // Send the address of Time Inactivity Low Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1;
// Raise SS for instruction completion
} void Write_Time_Inactivity_HReg (unsigned char DataByte) { LATBbits.LATB6 = 0; SPI_Transmit (0x0A);
// Lower SS for instruction delivery // Send the Write Register instruction
SPI_Transmit (0x26);
// Send the address of Time Inactivity High Register
SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void Write_Active_Inactive_Control_Reg (unsigned char DataByte) { LATBbits.LATB6 = 0;
// Lower SS for instruction delivery
SPI_Transmit (0x0A); SPI_Transmit (0x27);
// Send the Write Register instruction // Send the address of Active-Inactive Control Register
SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void Write_Filter_Control_Reg (unsigned char DataByte) { LATBbits.LATB6 = 0;
// Lower SS for instruction delivery
SPI_Transmit (0x0A); SPI_Transmit (0x2C);
// Send the Write Register instruction // Send the address of Filter Control Register
SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void Write_Power_Control_Reg (unsigned char DataByte) { LATBbits.LATB6 = 0;
// Lower SS for instruction delivery
SPI_Transmit (0x0A); SPI_Transmit (0x2D);
// Send the Write Register instruction // Send the address of Power Control Register
SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1;
// Raise SS for instruction completion
Peripherals
6.1
Accelerometer
191
} void Write_INTMAP1_Reg (unsigned char DataByte) { LATBbits.LATB6 = 0;
// Lower SS for instruction delivery
SPI_Transmit (0x0A); SPI_Transmit (0x2A);
// Send the Write Register instruction // Send the address of Interrupt Map 1 Register
SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; }
// Raise SS for instruction completion
void Write_INTMAP2_Reg (unsigned char DataByte) { LATBbits.LATB6 = 0;
// Lower SS for instruction delivery
SPI_Transmit (0x0A);
// Send the Write Register instruction
SPI_Transmit (0x2B); // Send the address of Interrupt Map 2 Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; }
// Raise SS for instruction completion
unsigned char Read_Status_Reg () { LATBbits.LATB6 = 0; SPI_Transmit (0x0B);
// Lower SS for instruction delivery // Send the Read Register instruction
SPI_Transmit (0x0B); // Send the address of Status Register unsigned char status = SPI_Receive (); // Read the status register LATBbits.LATB6 = 1; return status;
// Raise SS for instruction completion
} signed char Read_8bit_XData () { LATBbits.LATB6 = 0;
// Lower SS for instruction delivery
SPI_Transmit (0x0B); SPI_Transmit (0x08);
// Send the Read Register instruction // Send the address of XData Register
signed char XData = SPI_Receive (); // Read the SPI1BUF register LATBbits.LATB6 = 1; return XData;
// Raise SS for instruction completion
} signed char Read_8bit_YData () { LATBbits.LATB6 = 0;
// Lower SS for instruction delivery
SPI_Transmit (0x0B);
// Send the Read Register instruction
192
6
SPI_Transmit (0x09);
Peripherals
// Send the address of YData Register
signed char YData = SPI_Receive (); // Read the SPI1BUF register LATBbits.LATB6 = 1; // Raise SS for instruction completion return YData; } signed char Read_8bit_ZData () { LATBbits.LATB6 = 0; SPI_Transmit (0x0B);
// Lower SS for instruction delivery // Send the Read Register instruction
SPI_Transmit (0x0A); // Send the address of ZData Register signed char ZData = SPI_Receive (); // Read the SPI1BUF register LATBbits.LATB6 = 1; return ZData;
// Raise SS for instruction completion
} void main() { // OSCTUN Register OSCTUNbits.TUN = 0;
// select FRC = 7.37MHz
// CLKDIV Register CLKDIVbits.FRCDIV = 3; // FOSC = FRC/8 = 7.37MHz/8 and FP = FOSC/2 = 460KHz // SPI configuration MASTER mode sending 8 bits SPI1CON1bits.DISSCK = 0; // Enable the internal SPI clock SPI1CON1bits.DISSDO = 0;
// Enable the SPI data output, SDO
SPI1CON1bits.MODE16 = 0; // Enable the 8-bit data mode SPI1CON1bits.SSEN = 0; // This unit is not a slave so Slave Select pin is not used SPI1CON1bits.MSTEN = 1; SPI1CON1bits.SMP = 0;
// Enable MASTER mode // Sample data at the pos edge
SPI1CON1bits.CKE = 1; SPI1CON1bits.CKP = 0;
// Output data changes when SCK goes from ACTIVE to IDLE state // IDLE is low phase and ACTIVE is high phase of SCK
SPI1CON1bits.PPRE = 1;
// Primary SPI clock pre-scale is 1:16
SPI1CON1bits.SPRE = 7; // Secondary SPI clock pre-scale is 1:1 -> SCK = 460/16=29KHz SPI1STATbits.SPIROV = 0; // Clear initial overflow bit in case an overflow condition in SPI1BUF SPI1STATbits.SPIEN = 1;
// Enable the SPI interface
// Peripheral Pin Select with RP pins PPSUnLock; RPOR4bits.RP8R = 8;
// RP8 is an output for SCK1
RPINR20bits.SCK1R = 8; // RP8 is an input for SCK1
6.1
Accelerometer
RPOR4bits.RP9R = 7;
193 // RP9 is an output for SDO1
RPINR20bits.SDI1R = 7; // RP7 is an input for SDI1 RPOR3bits.RP6R = 9; // RP6 is an output for SS1 PPSLock; // Define I/O TRISBbits.TRISB6 = 0;
// Configure RB6 as an output for SS
TRISBbits.TRISB8 = 0;
// Configure RB8 as an output for SCK1
TRISBbits.TRISB9 = 0; TRISBbits.TRISB7 = 1;
// Configure RB9 as an output SDO1 // Configure RB7 as an input for SDI1
Write_Threshold_Activity_LReg (0x00);
// Assign 0mg to Threshold Activity Register // (always active because g>0mg in absolute mode)
Write_Threshold_Activity_HReg (0x00); Write_Threshold_Inactivity_LReg (0x00);
// Assign 0mg to Threshold Inactivity Register // (the device is never inactive)
Write_Threshold_Inactivity_HReg (0x00); Write_Time_Inactivity_LReg (0x0A); Write_Time_Inactivity_HReg (0x00); Write_Time_Activity_Reg (0x0A);
// Assign 10 samples in Inactivity Low Register // at 100Hz this translates to 0.1sec of inactivity // Assign 10 samples in Activity Register // at 100Hz this translates to 0.1sec of activity
Write_Active_Inactive_Control_Reg (0x05);// Default mode, Absolute mode, // Activity and Inactivity detections are both enabled Write_Filter_Control_Reg (0x03); Write_Power_Control_Reg (0x02);
// Range is 2g, LPF BW = half ODR BW, ODR = 100 samp/sec // Internal clock, normal operation, no wake-up and no auto-sleep // (Default mode = always awake), begin measurements
double X, Y, Z; double thetaX, thetaY, thetaZ; while ((Read_Status_Reg () & 0x01) == 0x00) Read_Status_Reg (); X = Read_8bit_XData ()*16; // X acceleration in mg Y = Read_8bit_YData ()*16; // Y acceleration in mg Z = Read_8bit_ZData ()*16; // Z acceleration in mg thetaX = (atan ((X)/sqrt(Y*Y + Z*Z)))*180.0/3.14; thetaY = (atan ((Y)/sqrt(X*X + Z*Z)))*180.0/3.14; thetaZ = (atan (sqrt(X*X + Y*Y)/(Z)))*180.0/3.14; }
The first section of Progam 6.1 describes the oscillator configuration in the microcontroller as shown in Fig. 6.18. In this program, we intend to use an internal FRC oscillator with 7.37 MHz clock frequency. The first pragma statement in this section, #pragma config FCKSM = 3, turn off the clock-switching and fail-safe modes. The second statement, #pragma config OSCIOFNC = 0, disables the OSCO
194
6
Peripherals
terminal from being a crystal pin, and converts it into a general-purpose I/O pin. The third statement, #pragma config POSCMD = 3, disables all primary oscillators that activate HS, XT or EC-type external crystals. The #pragma config IESO = 0 statement disables any internal-to-external oscillator switch-over mechanism since an internal FRC oscillator will be used to generate the system clock. Finally, the statement, #pragma config FNOSC = 7, defines the data-path for the internal FRC oscillator with FRCDIVN output. // The accelerometer continuously reads data (always AWAKE) in DEFAULT mode using SPI #include "xc.h" #include "math.h" // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled // Configuration Register - FOSCSEL #pragma config IESO = 0 // start with a user-selected oscillator at reset #pragma config FNOSC = 7 // select FRC oscillator with postscalar at reset … void main() { // OSCTUN Register OSCTUNbits.TUN = 0;
// select FRC = 7.37MHz
// CLKDIV Register CLKDIVbits.FRCDIV = 3; // FOSC = FRC/8 = 7.37MHz/8 and FP = FOSC/2 = 460KHz ... }
Fig. 6.18 Configuration of the system oscillator dspic33fj128mc802
Programming the OSCTUN oscillator register in the main section of the user program determines the internal FRC clock frequency. The OSCTUNbits.TUN = 0 statement tunes the FRC clock frequency at 7.37 MHz. The last statement, CLKDIVbits.FRCDIV = 3, divides the 7.37 MHz FRC clock frequency by eight, and produces 921.25 KHz at the FOSC port and 460.62 KHz at the FP port. The second section of the user program lists all the function calls to program the internal registers in ADXL362, and read the contents of the status and data registers.
6.1
Accelerometer
195
The first two function calls in this section describes how SPI1 transmits and receives data as shown in Fig. 6.19. Even though these functions are described in detail in Chapter 3, they will be mentioned here briefly. The SPI transmit function, SPI_Transmit, in this section starts by checking the Transmit Buffer Full bit, SPITBF, of the dspic33fj128mc802 status register in order to determine if the SPI1 transmit buffer is empty, using the while (SPI1STATbits. SPITBF == 1) statement. If the transmit buffer is not empty, the program waits, and continuously reads the SPITBF flag until it becomes empty. When it is empty, the program sends the contents of the user data, TxValue, to the SPI1BUF so that this buffer transmits each bit serially from the SDO port. As the SPI1 unit delivers data bits from its SDO port at the negative edge of SCK1, it simultaneously receives serial data from the SDI port into its shift register. These bits are considered “junk” during a transmit operation and ignored. Therefore, the program monitors the contents of the shift register, and checks if it is full of junk data, using a second while statement, while (SPI1STATbits.SPIRBF == 1). When full, the contents of the shift register are read by the statement, return SPI1BUF, to be disposed by the SPI1 master interface. The data receive operation is described in the second function, SPI_Receive. Again, the while (SPI1STATbits.SPITBF == 1) statement checks to see if the transmit buffer is empty, and waits until it becomes empty. The microcontroller sends a junk byte, 0x00, to the SPI1 transmit buffer, using the statement, SPI1BUF = 0x00. The byte, 0x00, can be any other value besides 0x00 because the contents of the transmit buffer is immaterial for the receive operation. As the junk bits of 0x00 are shifted out from the SDO port, valid bits are received into the shift register from the SDI port. The statement, while (SPI1STATbits.SPIRBF == 0), checks the SPIRBF bit in the status register to verify if the shift register is full of valid data. When full, this data is promptly transferred to the SP1BUF to be read by the SPI1 master interface.
196
6
Peripherals
// The accelerometer continuously reads data (always AWAKE) in DEFAULT mode using SPI #include "xc.h" #include "math.h" … unsigned char SPI_Transmit (unsigned char TxValue) { while (SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = TxValue; // When empty, send the byte to the TX buffer while (SPI1STATbits.SPIRBF == 0); // As valid bits shifts out of SDO, junk bits are received from SDI // Wait until the RX buffer is full of junk data return SPI1BUF; // When full, read the junk data in RX buffer through SPI1BUF } unsigned char SPI_Receive () { while(SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = 0x00; // When empty, send the junk byte (0x00) to the TX buffer while(SPI1STATbits.SPIRBF == 0); // As junk bits shifts out of SDO, valid bits are received from SDI // Wait until the RX buffer is full of valid data return SPI1BUF; // When full, read the valid data in RX buffer through SPI1BUF } void main() { … }
Fig. 6.19 SPI1 Transmit and Receive function calls
The next set of functions program the threshold activity and inactivity registers as shown in Fig. 6.20. Since activity and inactivity threshold values are 11 bits long, two registers are joined together to specify a threshold value for each entry as shown in Fig. 6.14. The first function, Write_Threshold_Activity_LReg, programs the lower eight bits of the activity threshold value. To start programming, the SPI protocol described in Fig. 6.17 must be followed. The statement, LATBbits.LATB6 = 0, lowers the RB6 pin to logic 0, and allows the programming to begin. Here, RB6 corresponds to the active-low slave select bit, SS (or CS in ADXL362). This is followed by the eight-bit write command, 0x0A, using the SPI Transmit function, SPI_Transmit (0x0A), described in Fig. 6.19. The register address, 0x20, is sent next by a second Transmit function, SPI_Transmit (0x20). The data to be written to the low Threshold Activity register is sent last by a third Transmit function, SPI_Transmit (DataByte). The statement, LATBbits.LATB6 = 1, raises the RB6 pin to logic 1, and terminates programming this register.
6.1
Accelerometer
// The accelerometer continuously reads data (always AWAKE) in DEFAULT mode using SPI #include "xc.h" #include "math.h" … void Write_Threshold_Activity_LReg (unsigned char DataByte) { LATBbits.LATB6 = 0; // Lower SS for instruction delivery SPI_Transmit (0x0A); // Send the Write Register instruction SPI_Transmit (0x20); // Send the address of Write Threshold Activity Low Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void Write_Threshold_Activity_HReg (unsigned char DataByte) { LATBbits.LATB6 = 0; // Lower SS for instruction delivery SPI_Transmit (0x0A); // Send the Write Register instruction SPI_Transmit (0x21); // Send the address of Write Threshold Activity High Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void Write_Threshold_Inactivity_LReg (unsigned char DataByte) { LATBbits.LATB6 = 0; // Lower SS for instruction delivery SPI_Transmit (0x0A); // Send the Write Register instruction SPI_Transmit (0x23); // Send the address of Write Threshold Inactivity Low Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void Write_Threshold_Inactivity_HReg (unsigned char DataByte) { LATBbits.LATB6 = 0; // Lower SS for instruction delivery SPI_Transmit (0x0A); // Send the Write Register instruction SPI_Transmit (0x24); // Send the address of Write Threshold Inactivity High Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void main() { … }
Fig. 6.20 Programming threshold activity and inactivity registers in ADXL362
197
198
6
Peripherals
The higher three bits of the activity threshold value is programmed into the second register by the Write_Threshold_Activity_HReg function. As was in the previous case, programming the high activity threshold register also takes three SPI Transmit functions, each sending the write command (0x0A), the register address (0x21) and the register data, all sandwiched between the LATBbits.LATB6 = 0 and LATBbits.LATB6 = 1 statements. The low and high inactivity registers are programmed similarly except for the register addresses and data as shown in Fig. 6.20. The Write_Threshold_Inactivity_ LReg function programs the lower eight bits of the inactivity threshold, and the Write_Threshold_Inactivity_HReg programs the higher three bits. Again, the microcontroller SPI1 interface uses the RB6 pin to enable the accelerometer in order to program these two registers. The next set of functions is related to programming specific time intervals for activity and inactivity as shown in Fig. 6.21. The activity and inactivity time registers are previously explained in Fig. 6.15. The time activity register is an 8-bit register located at the address 0x22. Therefore, once the accelerometer is enabled by the LATBbits.LATB6 = 0 statement, the three SPI Transmit functions, the write command, 0x0A, the register address, 0x22, and the register data, are sent out in a sequential manner within the same body of the Write_Time_Activity_Reg function. The same sequence takes place for the 16-bit inactivity time. This time, the lower eight bits of inactivity time interval are programmed into the low inactivity time register that resides at the address 0x25, using the Write_Time_Inactivity_LReg function, and the higher eight bits are programmed into the high inactivity time register at 0x26, using the Write_Time_Inactivity_HReg function.
6.1
Accelerometer
199
// The accelerometer continuously reads data (always AWAKE) in DEFAULT mode using SPI #include "xc.h" #include "math.h" … void Write_Time_Activity_Reg (unsigned char DataByte) { LATBbits.LATB6 = 0; // Lower SS for instruction delivery SPI_Transmit (0x0A); // Send the Write Register instruction SPI_Transmit (0x22); // Send the address of Time Activity Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void Write_Time_Inactivity_LReg (unsigned char DataByte) { LATBbits.LATB6 = 0; // Lower SS for instruction delivery SPI_Transmit (0x0A); // Send the Write Register instruction SPI_Transmit (0x25); // Send the address of Time Inactivity Low Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void Write_Time_Inactivity_HReg (unsigned char DataByte) { LATBbits.LATB6 = 0; // Lower SS for instruction delivery SPI_Transmit (0x0A); // Send the Write Register instruction SPI_Transmit (0x26); // Send the address of Time Inactivity High Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void main() { … }
Fig. 6.21 Programming time activity and inactivity registers in ADXL362
The fourth set of function calls program the vital control registers in ADXL362 as shown in Fig. 6.22. As was in the previous function calls, each function in this group also includes three SPI Transmit functions, and sent out in sequence, the write command, 0x0A, the register address and the data.
200
6
Peripherals
// The accelerometer continuously reads data (always AWAKE) in DEFAULT mode using SPI #include "xc.h" #include "math.h" … void Write_Active_Inactive_Control_Reg (unsigned char DataByte) { LATBbits.LATB6 = 0; // Lower SS for instruction delivery SPI_Transmit (0x0A); // Send the Write Register instruction SPI_Transmit (0x27); // Send the address of Active-Inactive Control Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void Write_Filter_Control_Reg (unsigned char DataByte) { LATBbits.LATB6 = 0; // Lower SS for instruction delivery SPI_Transmit (0x0A); // Send the Write Register instruction SPI_Transmit (0x2C); // Send the address of Filter Control Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void Write_Power_Control_Reg (unsigned char DataByte) { LATBbits.LATB6 = 0; // Lower SS for instruction delivery SPI_Transmit (0x0A); // Send the Write Register instruction SPI_Transmit (0x2D); // Send the address of Power Control Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void Write_INTMAP1_Reg (unsigned char DataByte) { LATBbits.LATB6 = 0; // Lower SS for instruction delivery SPI_Transmit (0x0A); // Send the Write Register instruction SPI_Transmit (0x2A); // Send the address of Interrupt Map 1 Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion }
Fig. 6.22 Programming control registers in ADXL362
6.1
Accelerometer
201
void Write_INTMAP2_Reg (unsigned char DataByte) { LATBbits.LATB6 = 0; // Lower SS for instruction delivery SPI_Transmit (0x0A); // Send the Write Register instruction SPI_Transmit (0x2B); // Send the address of Interrupt Map 2 Register SPI_Transmit (DataByte); // Send a data byte LATBbits.LATB6 = 1; // Raise SS for instruction completion } void main() { … }
Fig. 6.22 (continued)
The first function call in this set, Write_Active_Inactive_Control_Reg, programs the Active/Inactive control register at the address 0x27. The Filter control register at the address 0x2C, the Power control register at the 0x2D, the Interrupt 1 and Interrupt 2 Map registers at the 0x2A and 0x2B are all programmed in sequence by the function calls, Write_Filter_Control_Reg, Write_Power_Control_Reg, Write_INTMAP1_Reg and Write_INTMAP2_Reg, respectively. The functional details and the bit descriptions for each control register are given in Figs. 6.10, 6.11, 6.12, and 6.13. Reading the ADXL362 status and data registers is accomplished by the next set of function calls in Fig. 6.23. The function call, Read_Status-Reg, reads the contents of the status register as explained in Fig. 6.16. This function embodies three SPI functions. The first SPI function, SPI_Transmit (0x0B), sends the read command to the accelerometer. This is followed by the second SPI function, SPI_Transmit (0x0B), which sends the register address 0x0B. The third SPI function is a receive function, status = SPI_Receive, which stores the contents of the status register into a temporary register, status. The contents of the temporary register become available for the microprocessor, and therefore for the application program by the statement, return status. The last three function calls, Read_8bit_XData, Read_8bit_YData and Read_8bit_ZData, read the contents of the data registers, XDATA at the address 0x08, YDATA at the 0x09, and ZDATA at the 0x0A, from ADXL362, respectively. The functional details of these data registers are given earlier in Fig. 6.9. In each case, the read command, 0x0B, is followed by the register address value, and the contents of each data register. The acceleration data stored in the temporary registers, XData, YData and ZData, are subsequently read by the microcontroller, using the return statements, return XData, return YData and return ZData, respectively.
202
6
Peripherals
// The accelerometer continuously reads data (always AWAKE) in DEFAULT mode using SPI #include "xc.h" #include "math.h" … unsigned char Read_Status_Reg () { LATBbits.LATB6 = 0; SPI_Transmit (0x0B); SPI_Transmit (0x0B); unsigned char status = SPI_Receive (); LATBbits.LATB6 = 1; return status; }
// Lower SS for instruction delivery // Send the Read Register instruction // Send the address of Status Register // Read the status register // Raise SS for instruction completion
signed char Read_8bit_XData () { LATBbits.LATB6 = 0; SPI_Transmit (0x0B); SPI_Transmit (0x08); signed char XData = SPI_Receive (); LATBbits.LATB6 = 1; return XData; }
// Lower SS for instruction delivery // Send the Read Register instruction // Send the address of XData Register // Read the SPI1BUF register // Raise SS for instruction completion
signed char Read_8bit_YData () { LATBbits.LATB6 = 0; SPI_Transmit (0x0B); SPI_Transmit (0x09); signed char YData = SPI_Receive (); LATBbits.LATB6 = 1; return YData; }
// Lower SS for instruction delivery // Send the Read Register instruction // Send the address of YData Register // Read the SPI1BUF register // Raise SS for instruction completion
signed char Read_8bit_ZData () { LATBbits.LATB6 = 0; SPI_Transmit (0x0B); SPI_Transmit (0x0A);
// Lower SS for instruction delivery // Send the Read Register instruction // Send the address of ZData Register
signed char ZData = SPI_Receive (); // Read the SPI1BUF register LATBbits.LATB6 = 1; // Raise SS for instruction completion return ZData; } void main() { … }
Fig. 6.23 Reading status and data registers in ADXL362
6.1
Accelerometer
203
The next subsection of the user program sets up the SPI1 registers as shown in Fig. 6.24. Even though this section is described in detail in Chapter 3, we will briefly review it here to see what each statement accomplishes. The SPI1CONbits.DISSCK = 0 statement enables the SCK1 output from SPI1. The statement, SPI1CONbits.DISSDO = 0, enables the SDO port. The statement, SPI1CONbits.MODE16 = 0, defines the data transfer is achieved in 8-bit data format. The SPI1CONbits.SSEN = 0 statement removes the SS pin to be used as an input pin because SPI1 is a master interface. The statement, SPI1CONbits. MSTEN = 1, enables the master mode. The SPI1CONbits.SMP = 0 statement samples the data at the positive edge of SCK1, and delivers the data at the negative edge. The SPI1CONbits.CKE = 1 statement enables the master to produce data when SCK1 goes from the active to the idle state. The SPI1CONbits.CKP = 0 statement defines the idle state to be the low-phase, and the active state to be the high-phase of SCK1. When combined, these two instructions enable the master interface to deliver data at the negative edge of SCK1. The statements, SPI1CONbits.PPRE = 1 and SPI1CONbits.SPRE = 7, divides the SCK1 by 16. The statement, SPI1STATbits.SPIROV = 0, clears any overflow bit in the SPI1BUF register so the new data can be received and processed. Finally, the SPI1STATbits. SPIEN = 1 statement simply enables the SPI master interface to deliver data to the accelerometer. // The accelerometer continuously reads data (always AWAKE) in DEFAULT mode using SPI #include "xc.h" #include "math.h" … // SPI configuration MASTER mode sending 8 bits SPI1CON1bits.DISSCK = 0; // Enable the SPI clock, SCK1 SPI1CON1bits.DISSDO = 0; // Enable the SPI data output, SDO1 SPI1CON1bits.MODE16 = 0; // Enable the 8-bit data mode SPI1CON1bits.SSEN = 0; // This unit is not a slave so Slave Select pin is not used SPI1CON1bits.MSTEN = 1; // Enable MASTER mode SPI1CON1bits.SMP = 0; // Sample data at the pos edge when received at the neg edge of SCK SPI1CON1bits.CKE = 1; // SCK edge: Output data changes when SCK1 goes from ACTIVE to IDLE state SPI1CON1bits.CKP = 0; // SCK polarity: IDLE is low phase and ACTIVE is high phase of SCK SPI1CON1bits.PPRE = 1; // Primary SPI clock pre-scale is 1:16 SPI1CON1bits.SPRE = 7; // Secondary SPI clock pre-scale is 1:1 -> SCK1 = 460/16 = 29KHz SPI1STATbits.SPIROV = 0; // Clear initial overflow bit in case an overflow condition in SPI1BUF SPI1STATbits.SPIEN = 1; // Enable the SPI interface … }
Fig. 6.24 Setting up the SPI1 registers
204
6
Peripherals
The I/O configuration is described in Fig. 6.25. The I/O ports in dspic33fj128mc802 are equipped with Peripheral Pin Select (PPS) system. Therefore, the compiler directives, #define PPSUnlock and #define PPSLock, are used to be able to engage this system. The assignment of the physical RP pins that correspond to the SS, SCK, SDI and SDO ports are shown between the PPSUnLock and PPSLock statements.
// The accelerometer continuously reads data (always AWAKE) in DEFAULT mode using SPI #include "xc.h" #include "math.h" … #define PPSUnLock #define PPSLock
__builtin_write_OSCCONL(OSCCON & 0xBF) __builtin_write_OSCCONL(OSCCON | 0x40)
… void main() { … // Peripheral Pin Select with RP pins PPSUnLock; RPOR4bits.RP8R = 8; // RP8 is an output for SCK1 RPINR20bits.SCK1R = 8; // RP8 is an input for SCK1 RPOR4bits.RP9R = 7; // RP9 is an output for SDO1 RPINR20bits.SDI1R = 7; // RP7 is an input for SDI1 RPOR3bits.RP6R = 9; // RP6 is an output for SS1 PPSLock; // Define I/O TRISBbits.TRISB6 = 0; // Configure RB6 as an output for SS TRISBbits.TRISB8 = 0; // Configure RB8 as an output for SCK1 TRISBbits.TRISB9 = 0; // Configure RB9 as an output SDO1 TRISBbits.TRISB7 = 1; // Configure RB7 as an input for SDI1 … }
Fig. 6.25 SPI1 I/O configuration
6.1
Accelerometer
205
The first statement, RPOR4bits.RP8R = 8, in Fig. 6.25 assigns RP8 to the SCK1 output of the SPI1 interface. Microchip also recommends the clock pin to be an input as well as an output. Therefore, the second statement in this section, RPINR20bits. SCK1R = 8, defines the same physical pin to be a clock input for SPI1 as well. The statement, RPOR4bits.RP9R = 7, assigns RP9 to the SDO1 port. Similarly, the statement, RPINR20bits.SDI1R = 7, assigns RP7 to the SDI1 input. Finally, the statement, RPOR3bits.RP6R = 9, assigns RP6 to the SS1 port of the SPI1 interface. The TRIS-statements at the bottom part define the directionality of data, and configure the tri-state buffers to be inputs or outputs for SPI1. The TRISBbits. TRISB6 = 0 statement defines RB6 (or RP6) to be an output. Similarly, the statements, TRISBbits.TRISB8 = 0 and TRISBbits.TRISB9 = 0, configure RB8 (or RP8) and RB9 (or RP9) pins to be outputs, respectively. Only the TRISBbits. TRISB7 = 1 statement makes RB7 (or RP7) an input. The last section in the user program operates ADXL362 in default mode, continuously reading data from this unit’s data registers as shown in Fig. 6.26. The first two function calls, Write_Threshold_Activity_LReg and Write_Threshold_Activity_HReg, write 0x0000 to the Threshold Activity register. This way, we make the accelerometer always active when it operates in absolute mode since the gravity, g, acting on the device is always greater than 0mg. Similarly, in the next two statements the Threshold Inactivity register also receives 0x0000 by the Write_Threshold_Inactivity_LReg and Write_Threshold_Inactivity_HReg functions. This ensures that the device never goes into inactive state. The next three statements define the inactivity and activity time interval. The functions, Write_Time_Inactivity_LReg and Write_Time_Inactivity_HReg, assign a value of 10 samples for inactivity. However, this value does not mean anything because the device never becomes inactive due to the fact that the accelerometer operates in the absolute mode, and therefore the value stored in the Inactivity Threshold register becomes immaterial. However, the value stored in the Activity Time register matters. The function call, Write_Time_Activity_Reg ensures there are 10 samples stored in the Time Activity register. The function, Write_Active_Inactive_Control_Reg, writes 0x05 to the Active/ Inactive control register. This translates the default and the absolute modes to be engaged during the device operation, and both activity and inactivity features to be present according to Fig. 6.10. However, the inactivity feature never takes place as mentioned above. The Write_Filter_Control_Reg function writes 0x03 to the Filter Control register. This setting adjusts the gravity range to be +/-2g, and the ODR to be 100 Hz or 100 samples per second as shown in Fig. 6.12. Therefore, the Activity Time interval becomes 0.1msec since only 10 samples are stored in the Time Activity register. Finally, the function call, Write_Power_Control_Reg, writes 0x02 to the Power Control register. This value basically retains the internal clock, disables low noise operation, and starts the accelerometer for taking measurements according to Fig. 6.13. We did not write anything to the Interrupt 1 or Interrupt 2 Map registers because interrupts are not used in this program.
206
6
Peripherals
// The accelerometer continuously reads data (always AWAKE) in DEFAULT mode using SPI #include "xc.h" #include "math.h" … void main() { … Write_Threshold_Activity_LReg (0x00); Write_Threshold_Activity_HReg (0x00); Write_Threshold_Inactivity_LReg (0x00); Write_Threshold_Inactivity_HReg (0x00); Write_Time_Inactivity_LReg (0x0A);
// Assign 0mg to Threshold Activity Register // (always active because g > 0mg in absolute mode) // Assign 0mg to Threshold Inactivity Register (the device is never inactive) // Assign 10 samples in Inactivity Low Register at 100Hz this translates to 0.1sec // of inactivity
Write_Time_Inactivity_HReg (0x00); Write_Time_Activity_Reg (0x0A);
// Assign 10 samples in Activity Register, at 100Hz this translates to 0.1sec // of activity Write_Active_Inactive_Control_Reg (0x05); // Default mode, Absolute mode, Activity and Inactivity detections are both enabled Write_Filter_Control_Reg (0x03); // Range is 2g, LPF BW = half ODR BW, ODR = 100 samples/sec Write_Power_Control_Reg (0x02); // Internal clock, normal operation, Default mode = always awake, begin measurements double X, Y, Z; double thetaX, thetaY, thetaZ; while ((Read_Status_Reg () & 0x01) == 0x00) Read_Status_Reg (); X = Read_8bit_XData ()*16; // Shift left by 4 bits (multiply by 16) to compute X acceleration in 12-bit format Y = Read_8bit_YData ()*16; // Shift left by 4 bits (multiply by 16) to compute Y acceleration in 12-bit format Z = Read_8bit_ZData ()*16; // Shift left by 4 bits (multiply by 16) to compute Z acceleration in 12-bit format thetaX = (atan ((X)/sqrt(Y*Y + Z*Z)))*180.0/3.14; thetaY = (atan ((Y)/sqrt(X*X + Z*Z)))*180.0/3.14; thetaZ = (atan (sqrt(X*X + Y*Y)/(Z)))*180.0/3.14; }
Fig. 6.26 ADXL362 operation in default mode
This program only takes continuous measurements with respect to the x, y and z axes, and the application program computes the angles according to Eqs. 6.11, 6.12 and 6.20. As demonstrated in the previous programming examples, the status register plays a vital role in monitoring the device behavior, and therefore guiding the application program to run as intended. In this case, the while ((Read_Status_Reg () & 0x01) == 0x00) statement masks the least significant bit of the status register, DATA_READY, and monitors this bit continuously. As long as this bit is at logic 0, the program reads the contents of the status register by the Read_Status_Reg statement. However, when DATA_READY = 1 then the program reads the values of XDATA, YDATA and ZDATA registers, using the functions, Read_8bit_XData, Read_8bit_YData and Read_8bit_ZData, and stores them in the temporary X, Y, Z
6.2
Pixy Camera
207
registers, respectively. The acceleration angles, ΘX, ΘY and ΘZ, are subsequently calculated using Eqs. 6.11, 6.12 and 6.20 where each angle is multiplied by 180/π to convert the floating point number into degrees.
6.2
Pixy Camera
In order to charter a path for a mobile robot, the most important tool is an image recognition camera. To demonstrate the necessity of such a device we will use the simplest image recognition camera, CMU Pixy, manufactured by Carnagie Mellon University as shown in Fig. 6.27 [4].
Fig. 6.27 CMU Pixy camera – version 5 on a stand
We installed the camera on a stand, and directed its lens to observe a stationary object, in this case a golf ball painted in red and erected on a stand as shown in Fig. 6.28. The camera has two connection ports, one with USB, and the other with SPI or I2C. Since the camera will be used for mobile robot applications, we opted to activate it using the SPI port connection on the back of its printed circuit board as shown in the figure.
208
6
Peripherals
Fig. 6.28 Pixy camera under test
Fig. 6.29 shows the “Raw” image of the golf ball. Row image feature is one of the three imaging features of the camera, and shows objects in front of the lens without any post processing. At this point, the camera is connected to the PC with a USB cord, and the camera’s Graphics User Interface (GUI), called PixyMon, is able to display the image on the computer screen.
6.2
Pixy Camera
209
Fig. 6.29 Pixy camera “Raw” image mode
Once the object signature is activated using PixyMon, the Raw image mode can be changed around. The “Cooked” image mode in Fig. 6.30 consists of the Raw image of the ball with a box around it. In order to reach this mode, the object signature (the first object property) must be named first. This is accomplished by choosing the Configure button under the File option on PixyMon. Once there, any one of the seven signature labels can be named with a mixture of letters and numbers. The next step is to go to the Action button on PixyMon, and select the corresponding Set Signature option. For example, if signature label 1 is chosen and named during the configuration phase, selecting the corresponding set signature 1 option under the Action button will change the cursor function to select a part (or all) of the image to be recognized. When the object framing step is complete, the cursor goes back to its normal mode, and a box forms around the object with the signature label on PixMon. Note that the image of the object is not smooth as in Fig. 6.29 but quite pixilated. The box named “1” in Fig. 6.30 may also jump around quite a bit depending on the lighting condition surrounding the object. The user is encouraged to expose sufficient light on the object while the recognition process is taking place, and use vibrant colors because dull objects with muted colors will not be recognized by the camera.
210
6
Peripherals
Fig. 6.30 Pixy camera “Cooked” image mode
The third image mode is the “Default” mode as shown in Fig. 6.31. This is a simple rectangle whose interior is painted with the same color as the object, and named after the signature label. This is actually the data sent out from the camera’s SPI interface to the microcontroller to recognize and track the image.
6.2
Pixy Camera
211
Fig. 6.31 Pixy camera “Default” image mode with object signature
The user can also select a color signature instead of an object signature under the Action button. This mode skips the box around the object and reveals an image in a pixilated format as shown in Fig. 6.32. This is opposite of the smooth image in Fig. 6.29 when two modes are compared against each other. When the color signature is engaged, the camera produces the color code of the image as opposed to its boundary dimensions as in Fig. 6.31.
212
6
Peripherals
Fig. 6.32 Pixy camera “Default” image mode with color signature
Experienced users are able to adjust any of the seven object signatures in Fig. 6.33, using a signature range that changes between 0 and 11. The default value, 3, does a satisfactory job in defining the object rectangle in object mode or the color signature in color mode. Increasing the number in signature range increases the sensitivity of recognition, but also causes erroneous recognition patterns to form outside the object boundary. Users are encouraged to change the sensitivity range and minimum brightness to obtain optimum recognition patterns for a particular object.
6.2
Pixy Camera
213
Fig. 6.33 Pixy camera parameters
To explain the full camera functionality, two simple application programs were developed. The first application is a “limited” version of Program 6.2 where only one servo motor (pan servo) is used. This application allows a mobile robot to follow a fast-moving red ball in Fig. 6.28. For this purpose, the camera is not allowed to move in x or y-directions, but firmly mounted on a three-wheel robotic chassis with two large front wheels, and a small independent rear wheel connected to a servo motor. The program runs the camera in default mode, and constantly forces the camera to keep the ball at the center of PixyMon by steering the rear servo motor. It also calculates the location and dimensions of the ball as the ball approaches or moves away from the camera to facilitate the centering and steering calculations. The chassis is composed of the microcontroller, the CMU Pixy camera, a steering servo, and two electric motors that provide the actuation (one motor per front wheel) as shown in Fig. 6.34. The close-up picture in Fig. 6.35 shows how the steering servo is connected to the rear wheel at the back of the robotic chasis. Note that the exact same hardware and application program developed for this robot can be used to design a line-follower where the robot follows a continuous white line on the ground. The second application enables the camera to acquire and track all possible movements of the red ball as it moves in a three-dimensional space. For
214
Fig. 6.34 Ball-follower robot
Fig. 6.35 Servo steering mechanism in the ball-follower robot
6
Peripherals
6.2
Pixy Camera
215
this application, the camera is not mounted on a mobile robot platform, but fixed on the ground. To enable the camera movements in x and y-directions, two servos (pan servo and tilt servo) are mounted under the camera in Program 6.2. Because of its length, the application program in Program 6.2 is divided into five sub-sections for clarity. Program 6.2 // This is a simplified object tracking program in x-axis with a single servo // There is only one equation for pulse width for the pan servo as a function of X // Pixy camera receives 6 words of data from a single object after double sync codes // (1 for frame and 1 for each object) // Bytes 0, 1 = sync word (0xAA55) // Bytes 2, 3 = check sum (sum of bytes 6 to 13) // Bytes 4, 5 = signature number (signature of the object) // Bytes 6, 7 = X center of the object // Bytes 8, 9 = Y center of the object // Bytes 10, 11 = width of the object // Bytes 12, 13 = width of the object #include "xc.h" #include "stdlib.h" // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled // Configuration Register - FOSCSEL #pragma config IESO = 0
// start with a user-selected oscillator at reset
#pragma config FNOSC = 7
// select FRC oscillator with postscalar at reset
// Configuration Register - FICD #pragma config JTAGEN = 0 // JTAG is disabled #pragma config FWDTEN = 0 // Disable Watch Dog timer #pragma config IOL1WAY = 0 // Allow multiple PPS reconfigurations #pragma config GSS = 3 #pragma config GWRP = 1 #pragma config ICS = 3 #define PPSUnLock #define PPSLock
// User program is not code-protected // User program is not write-protected // Program with PGEC1/PGED1 program ports
__builtin_write_OSCCONL(OSCCON & 0xBF) __builtin_write_OSCCONL(OSCCON | 0x40)
unsigned int i, j, k; unsigned int HighByte, LowByte;
216
6
Peripherals
unsigned int CHKSUM; unsigned int SIG; unsigned int X, X0; unsigned int Y; unsigned int W; unsigned int H; unsigned int DeltaX, DeltaY; unsigned int StartWord = 0xAA55; unsigned int Word[100]; unsigned char SPI_Exchange (unsigned char Value) { while (SPI1STATbits.SPITBF == 1); // Wait until the TX buffer is empty due to a prior process SPI1BUF = Value; // When empty, send the sync byte to the TX buffer while (SPI1STATbits.SPIRBF == 0); // As sync bits shifts out of the SDO port of the CPU, // pixy bits are received from the SDI port // Wait until the RX buffer is full of pixy data return SPI1BUF; }
// When full, read the pixy data in RX buffer through SPI1BUF
int main() { // OSCTUN Register OSCTUNbits.TUN = 21; // select FRC = 8.0MHz CLKDIVbits.FRCDIV = 3; // FOSC = 1MHz, FP = 500KHz CLKDIVbits.DOZE = 2; // FCY = 125KHz (CPU clock) CLKDIVbits.DOZEN = 1; // Enable CPU clock // Program Timer3 control register T3CONbits.TGATE = 0; // Gated (with external clock) timer3 clock is disabled T3CONbits.TCKPS = 0; // Pre-scale is 1:1 so the Timer3 clock = FP = 500KHz T3CONbits.TCS = 0; // Timer3 works with the internal clock = FP = 500KHz // Program Timer3 time base TMR3 = 0;
// Initial value in Timer3 counter register
PR3 = 10148;
// Timer3 period register produces 20msec PWM period
// Configure Output Compare1 control register to work with Timer3, OC1CON OC1CONbits.OCTSEL = 1; // Timer3 is selected for output compare1 OC2CONbits.OCTSEL = 1; // Timer3 is selected for output compare2 OC1CONbits.OCM = 6; // Output compare1 is in PWM mode with Fault pin disabled OC2CONbits.OCM = 6;
// Output compare2 is in PWM mode with Fault pin disabled
6.2
Pixy Camera
217
// Peripheral Pin Select with RP pins PPSUnLock; RPOR4bits.RP8R = 8;
// RB8 is for SCK1 (output))
RPINR20bits.SCK1R = 8; // RB8 is for SCK1 (input)) RPOR4bits.RP9R = 7; // RB9 is for SDO1 RPINR20bits.SDI1R = 7; // RB7 is for SDI1 RPOR3bits.RP6R = 9; // RB6 is for SS1 RPOR2bits.RP4R = 18;
// RB4 is for OC1 output for PAN servo
RPOR2bits.RP5R = 19; PPSLock;
// RB5 is for OC2 output for TILT servo
// Define I/O TRISBbits.TRISB6 = 0; // Configure RB6 as an output for SS TRISBbits.TRISB8 = 0; // Configure RB8 as an output for SCK1 TRISBbits.TRISB9 = 0; // Configure RB9 as an output SDO1 TRISBbits.TRISB7 = 1; // Configure RB7 as an input for SDI1 TRISBbits.TRISB4 = 0; // Configure RB4 as an output for OC1 or PAN servo TRISBbits.TRISB5 = 0; // Configure RB5 as an output for OC2 or TILT servo // SPI configuration MASTER mode sending 8 bits SPI1CON1bits.DISSCK = 0; // Enable the internal SPI clock SPI1CON1bits.DISSDO = 0; // Enable the SPI data output, SDO SPI1CON1bits.MODE16 = 0; // Enable the 8-bit data mode SPI1CON1bits.SSEN = 0; // This unit is not a slave so Slave Select pin is not used SPI1CON1bits.MSTEN = 1; // Enable MASTER mode SPI1CON1bits.SMP = 0; // Sample input in the middle of data output period SPI1CON1bits.CKE = 0;
// (data is sampled at the pos edge when received at the neg edge of SCK) // SCK edge: Output data changes when SCK goes from IDLE to ACTIVE
SPI1CON1bits.CKP = 0; SPI1CON1bits.PPRE = 3;
// SCK polarity: IDLE is low phase and ACTIVE is high phase of SCK // Primary SPI clock pre-scale is 1:1
// (data is transmitted at the pos edge of SCK)
SPI1CON1bits.SPRE = 3; // Secondary SPI clock pre-scale is 1:5 -> SCK = FCY/5 = 25KHz SPI1STATbits.SPIROV = 0; // Clear initial overflow bit in case an overflow condition in SPI1BUF SPI1STATbits.SPIEN = 1;
// Enable the SPI interface
// Enable Timer3 T3CONbits.TON = 1; // Timer3 starts operating with the internal clock = FP // Initial object location should be in the middle of x axis at the pixel location 159 // between pixel 0 and pixel 319 X0 = 159; // Ping the camera with 50 words (0x5A00) to get a camera response, // and to see if the object has moved to a new location - this consitutes a FRAME
218
6
Peripherals
ACQUIRE: for (j = 0; j < 50; j++) { LATBbits.LATB6 = 0; // Lower SS to send a ping word = 0x5A00 HighByte = SPI_Exchange(0x5A); LowByte = SPI_Exchange(0x00); Word[j] = (HighByte SCK = FCY/5 = 25KHz SPI1STATbits.SPIROV = 0; // Clear initial overflow bit in case an overflow condition in SPI1BUF SPI1STATbits.SPIEN = 1; // Enable the SPI interface … }
Fig. 6.39 dspic33fj128mc802 SPI1 interface setup for Pixy camera
Note that the camera’s serial interface must be set to SPI mode before the camera is enabled to send data to the microcontroller. This is accomplished by selecting the Configure button under File on PixMon. Once there, the “SPI with SS” interface option is selected to engage the four-wire SPI communication protocol with the microcontroller. The camera can also be operated in pure analog mode where the x-axis or in y-axis coordinate of the object is produced as an analog signal. The other interesting option under the Configure button is the “Blocks” option. This option can recognize (and send object box information) up to 1000 objects and 1000 blocks per signature to dspic33fj128mc802.
224
6
Peripherals
In object recognition mode, the camera sends out six words to the microcontroller to describe the object location, its dimensions, and color within a 320x200 pixel screen as shown in Fig. 6.40. The top leftmost pixel on the screen corresponds to pixel (0, 0), and the bottom rightmost pixel corresponds to (319, 199) in this figure. Prior to any data exchange, the microcontroller first transmits a 16-bit word, 0x5A00 to wake up the camera, and establish a “healthy” communication link for data exchange. The camera responds to the microcontroller’s “ping” with a preamble, 0xAA55, followed by the object data. The object data consists of six words. The first word is called the “Check Sum”, that basically adds the hexadecimal values of the four words about the object: X center of the object, Y center of the object (both coordinates are with respect to the 320x200 screen dimensions), the width of the object, and the height of the object. Then the camera sends out the remaining five words, the signature label (color definition of the object), the X center, the Y center, the width, and the height of the object in a sequential manner. Fig. 6.40 Screen coordinates in X-pixels and Y-Pixels in Pixy camera
(0, 0)
(159, 0)
(319, 0)
(0, 99)
(159, 99)
(319, 99)
(0, 199)
(159, 199)
(319, 199)
Before we describe the fifth section of the user program that detects an object, and moves the camera to track the object’s movements, we need to explain the mechanics of the two servos that enable the camera motion. In order to achieve a complete three dimensional motion of the camera, this system uses two servos. Both servos are mounted below the camera deck at a safe distance from the camera to prevent electromagnetic interference. The first servo rotates the camera between -45° and +45° in x-axis and it is called the “pan” servo. The second servo rotates the camera between -45° and +45° in y-axis, and it is called the “tilt” servo as shown in Fig. 6.41. Both servos operate with PWM signals as described in Chapter 7. In a conventional analog servo, 1ms pulse width of a 20ms square-wave period corresponds to the servo arm motion of -45° or all the way to the left, and 2ms pulse width corresponds to the servo arm motion of +45° or all the way to the right. A 1.5ms pulse width pins the servo arm at a neutral or 0° position. In this figure, the camera “paints” a screen, by producing 320 pixels in the X-direction from pixel 0 to pixel 319, and 200 pixels in Y-direction from pixel 0 to pixel 199.
6.2
Pixy Camera
225
As we pointed out earlier, we recommend the reader to be familiar with the Pulse Width Modulation (PWM), Output Compare units, timers and servos discussed in the next chapter before continuing the rest of this section. X = 0 (PW = 1ms)
X = 159 (PW = 1.5ms)
Y = 0 (PW = 1ms)
X = 319 (PW = 2ms) Top side
Left side
Right side
-45o +45o -45o Y = 99 (PW = 1.5ms)
+45o Camera Tilt servo motion
Camera Pan servo motion
Bottom side Y = 199 (PW = 2ms)
Fig. 6.41 The pan and tilt servo motions, and screen boundaries
Both pan and tilt servos are assumed to use Timer 3 in this section to generate their PWM signals. Theoretical duty cycles for this timer can be calculated as follows. Assuming that Timer 3 is programmed to operate at 500 KHz, or a period of 2 μs, the duty cycle to keep the servo arm at the neutral position will be 1500 μs / 2 μs = 750. To rotate the servo to the left (or to the top) by -45° the timer will require a duty cycle of 1000 μs / 2 μs = 500. Similarly, to rotate the same servo to the right (or to the bottom) by +45° the duty cycle will be 2000 μs / 2 μs = 1000 according to Fig. 6.41. Theoretical duty cycle for the pan servo as a function of X-axis pixel position can easily be derived with the following equation with two unknowns, A and B, according to Fig. 6.41: DCPAN = A X þ B
ð6:21Þ
We can compute A and B with the boundary conditions below: At X = 0 At X = 319
DCPAN = 500, and DCPAN = 1000.
Using these values in equation (6.21), we can find A = 1.57 and B = 500. Thus: DCPAN = 1:57 X þ 500
ð6:22Þ
226
6
Peripherals
Similarly, the theoretical duty cycle for the tilt servo can be calculated, using a similar equation with two unknowns, C and D, below: DCTILT = C Y þ D
ð6:23Þ
We can compute C and D using the boundary conditions: At Y = 0 At Y = 199
DCTILT = 500, and DCTILT = 1000.
Using these values in equation (6.23), we can find C = 2.51 and D = 500. Therefore, DCTILT = 2:51 Y þ 500
ð6:24Þ
The theoretical duty cycle equations for DCPAN and DCTILT above constitute a starting point to operate Timer 3 for the pan and tilt servos in the ballpark. In reality, when the theoretical duty cycle values are used for Timer 3, the timer outputs produced slightly smaller pulse widths for the pan and tilt servos, and therefore the DCPAN and DCTILT equations in (6.22) and (6.24) are slightly modified as shown below: DCPAN = 1:59 X þ 508
ð6:25Þ
DCTILT = 2:56 Y þ 507
ð6:26Þ
Fig. 6.42 explains the algorithm of acquiring the location of the red ball while it moves along the x-axis, and determining its dimensions. The data acquisition algorithm in Fig. 6.42 starts with placing the ball right across from the camera at the X0 = 159 pixel location as this point sets the initial position. When j = 0, the microcontroller generates 50 ping words, 0x5A00, and sends them to the camera from its SPI output. In the algorithm, this is called a frame. The number of words in a frame can be altered depending on the SPI bus speed, processor speed, and the interface quality with the camera. For every ping word, the camera responds by either sending a start word, 0xAA55, without any object data, or the start word followed by the object data to the microcontroller’s SPI input. If the first two words from the camera are equal to 0xAA55, then the microcontroller fetches the object data composed of six words: Check Sum, Color Signature, X and Y locations of the ball (although the Y location will be ignored in the first user application as mentioned earlier), the width and the height, and stores them into CHKSUM, SIG, X, Y, W and H registers, respectively. The processor computes ΔX = X – X0 to determine any ball movement in the x-axis. If it finds ΔX is small (i.e. less than five pixels), then it concludes that the ball has not moved, and keeps the duty cycle, DCPAN, corresponding to the initial ball location at X0 the same. This action does not move the servo arm, and the microcontroller goes back to construct a new frame. If the microcontroller finds ΔX significant (i. e. larger than five pixels),
6.2
Pixy Camera
227
then it decides that the ball has moved, computes a new duty cycle for the pan servo according to the X value it just fetched (instead X0), and waits for the servo arm to move before constructing a new frame. If the first two words of the frame are not equal to 0xAA55, then the program increments to j = j + 1 = 1, which prompts the microcontroller to test the second and the third words of the “same” frame. If the microcontroller finds both of them equal to 0xAA55, then it computes ΔX = X – X0 again, and decides if this value is insignificant or not. If it finds ΔX insignificant, it does not produce a command to move the servo arm. However, if it finds this value to be over the user defined limit then it computes a new DCPAN, and uses this value in the OC1RS register to rotate the servo arm to a new position that points the ball location. This search and fetch process goes all the way to j = 42 as shown in Fig. 6.42. At this stage, if the processor still cannot locate two identical start words to fetch the object data, the user either needs to increase the j-limit, or inspect the camera SPI interface to see if any data corruption has been taking place. The section of the user program written according to the algorithm in Fig. 6.42 is subdivided into several segments to achieve greater clarity. In the first subsection, the microcontroller tries to establish a link with the camera by continuously transmitting two serial words, 0x5A and 0x00, as shown in Fig. 6.43. Most times the camera does not respond with any real object data, but only sends back the start code, 0xAA55, to the microcontroller. Therefore, the user program should repeat sending the start code, 0x5A00, for a good number of times (50 times according in Fig. 6.43) until the camera responds with real data.
228
6
X0 = 159
0x5A00
0x5A00
H
0x5A00
0x5A00
W
0x5A00
0x5A00
Y
0x5A00
0x5A00
X
0x5A00
0x5A00
0x5A00
0x5A00
0x5A00
CHKSUM
SIG
0x5A00 Word1
Processor sends 50 0x5A00 ping words Processor receives words from the camera
FRAME
Word2
j=0
Peripherals
Check if Word1 = Word2 = 0xAA55 If YES, then compute X = X –X0 If X 0 then this indicates no ball movement, X0 Wait until the servo arm moves
DCPAN (OC1RS)
0x5A00
H
0x5A00
W
0x5A00
Y
0x5A00
X
0x5A00
0x5A00 SIG
0x5A00
0x5A00 CHKSUM
0x5A00
0x5A00 Word2
0x5A00
0x5A00
0x5A00
0x5A00 No!
Word1
If NO, then j = j +1 (j = 1), and search for the beginning of camera data in the same frame
Check if Word1 = Word2 = 0xAA55 If YES, then compute X = X –X0 If X 0 then this indicates no ball movement, X0 Wait until the servo arm moves If X 0 then this indicates ball movement, X Wait until the servo arm moves
DCPAN (OC1RS)
DCPAN (OC1RS)
H
0x5A00
W
0x5A00
0x5A00
Y
0x5A00
0x5A00
X
0x5A00
0x5A00
0x5A00 CHKSUM
0x5A00
0x5A00 Word2
0x5A00
0x5A00 Word1
SIG
0x5A00
0x5A00 No!
No!
If NO, then j = j +1 (j = 2), and search for the beginning of camera data in the same frame
Check if Word1 = Word2 = 0xAA55 If YES, then compute X = X –X0 DCPAN (OC1RS)
If X 0 then this indicates no ball movement, X0 Wait until the servo arm moves If X 0 then this indicates ball movement, X Wait until the servo arm moves
DCPAN (OC1RS)
0x5A00
0x5A00
0x5A00
0x5A00
0x5A00
0x5A00 Word2
CHKSUM
0x5A00
0x5A00 Word1
Fig. 6.42 A flow chart to locate an object, and track its movements
SIG
0x5A00 No!
0x5A00 No!
0x5A00 No!
0x5A00
0x5A00 No!
No!
No!
0x5A00
If NO, then j = j +1 (j = 42), and search for the beginning of camera data in the same frame
X
Y
W
H
Construct a new FRAME with a set of 50 pings
If X 0 then this indicates ball movement, X Wait until the servo arm moves
DCPAN (OC1RS)
6.2
Pixy Camera
229
// Pixy camera receives 6 words of data from a single object after double sync codes // (1 for frame and 1 for each object) … int main() { … // Ping the camera with another 50 words (0x5A00) to see if the object has moved to a new location ACQUIRE: for (j = 0; j < 50; j++) { LATBbits.LATB6 = 0;
// Lower SS to send a ping word = 0x5A00
HighByte = SPI_Exchange(0x5A); LowByte = SPI_Exchange(0x00); Word[j] = (HighByte SCK = FCY/5 = 25KHz
SPI1STATbits.SPIROV = 0; // Clear initial overflow bit in case an overflow condition in SPI1BUF SPI1STATbits.SPIEN = 1; // Enable the SPI interface // Enable Timer3 T3CONbits.TON = 1; // Timer3 starts operating with the internal clock = FP // Initial Pan Servo position X0 = 159; DeltaTheta = 0; Pan_Servo = 761; // Ping the camera with 50 words (0x5A00) to get a response ACQUIRE:
244
6
for (i = 0; i < 50; i++) { LATBbits.LATB6 = 0;
// Lower SS to send a ping word = 0x5A00 to the camera
HighByte = SPI_Exchange(0x5A); LowByte = SPI_Exchange(0x00); Word[i] = (HighByte 170 || X < 150) { DeltaX = X - X0; if (d > 0 && d < 11.5) Xphy = 0.0042*DeltaX*d; else if (d >= 11.5 && d < 21.5) Xphy = DeltaX*(0.00425*(d-11.5)+0.048); else if (d >= 21.5 && d < 31.5) Xphy = DeltaX*(0.004*(d-21.5)+0.091); else if (d >= 31.5 && d < 41.5) Xphy = DeltaX*(0.0039*(d-31.5)+0.131); else if (d >= 41.5 && d < 51.5) Xphy = DeltaX*(0.004*(d-41.5)+0.17); else if (d >= 51.5 && d < 61.5) Xphy = DeltaX*(0.0042*(d-51.5)+0.21); else // d >= 61.5 && d < 71.5 Xphy = DeltaX*(0.004*(d-61.5)+0.252); Theta = (180/3.14)*asin (Xphy/d); DeltaTheta = DeltaTheta + Theta; XphyPrime = d * sin((3.14*DeltaTheta)/180); if (d > 0 && d < 11.5) DeltaXPrime = XphyPrime / (0.0042*d); else if (d >= 11.5 && d < 21.5) DeltaXPrime = XphyPrime / (0.00425*(d-11.5)+0.048);
Peripherals
6.2
Pixy Camera
245
else if (d >= 21.5 && d < 31.5) DeltaXPrime = XphyPrime / (0.004*(d-21.5)+0.091); else if (d >= 31.5 && d < 41.5) DeltaXPrime = XphyPrime / (0.0039*(d-31.5)+0.131); else if (d >= 41.5 && d < 51.5) DeltaXPrime = XphyPrime / (0.004*(d-41.5)+0.17); else if (d >= 51.5 && d < 61.5) DeltaXPrime = XphyPrime / (0.0042*(d-51.5)+0.21); else //d >= 61.5 && d < 71.5) DeltaXPrime = XphyPrime / (0.004*(d-61.5)+0.252); PS = 1.59*(DeltaXPrime + X0) + 508; Pan_Servo = PS; for (j = 0; j < 5000; j = j + 1) // j < 1000 produces lots of jerkiness; j < 2000 to 4000 still some OC1RS = Pan_Servo; WAIT(); // Wait for the servo arm to settle goto ACQUIRE; } else // 150 < X < 170 { OC1RS = Pan_Servo; goto ACQUIRE; } } else { i = i + 1; goto SCAN; } }
The statements to set up the oscillator, SPI bus, Timer 3, microcontroller I/O in Program 6.3 are the same as in Program 6.2; therefore, they will not be repeated here. The same statement applies to the camera “pinging” process by transmitting 50 words of 0x5A00, and acquiring an object data enveloped by two start words, 0xAA55 and 0xAA55. Therefore, these sections are either skipped, or not highlighted in Fig. 6.54. The rest of the program is developed according to the algorithm in Fig. 6.53. At the beginning of the program in Fig. 6.54, the parameters, X0 = 159, DeltaTheta = 0 and Pan Servo = 761, signifiy the initial X, ΔΘ and pan servo duty cycle values to be used in the program, respectively. Then the camera goes into the acquision mode, and retrieves X, Y, H and W of the object according to STEP 1 of Fig. 6.53. Next, d is calculated according to Equation (6.27) and STEP 2 of Fig. 6.53. The if-statement, if (X > 170 || X < 150), specifies if the acquired X is outside of X0 = 159 by about +/- 10 pixels. If so, then the program calculates DeltaX, ΔX = X - 159, according to STEP 3 of Fig. 6.53. Next, the physical X distance, Xphy, is calculated in reference to the Eqs. (6.29) through (6.35) and STEP
246
6
// This is an object tracking program with a single pan servo … // Initial Pan Servo position X0 = 159; DeltaTheta = 0; Pan_Servo = 761; … d = 0.22 + (855.3/H); if (X > 170 || X < 150) { DeltaX = X - X0; if (d > 0 && d < 11.5) Xphy = 0.0042*DeltaX*d; else if (d >= 11.5 && d < 21.5) Xphy = DeltaX*(0.00425*(d-11.5)+0.048); else if (d >= 21.5 && d < 31.5) Xphy = DeltaX*(0.004*(d-21.5)+0.091); else if (d >= 31.5 && d < 41.5) Xphy = DeltaX*(0.0039*(d-31.5)+0.131); else if (d >= 41.5 && d < 51.5) Xphy = DeltaX*(0.004*(d-41.5)+0.17); else if (d >= 51.5 && d < 61.5) Xphy = DeltaX*(0.0042*(d-51.5)+0.21); else // d >= 61.5 && d < 71.5 Xphy = DeltaX*(0.004*(d-61.5)+0.252); Theta = (180/3.14)*asin (Xphy/d); DeltaTheta = DeltaTheta + Theta; XphyPrime = d * sin((3.14*DeltaTheta)/180); if (d > 0 && d < 11.5) DeltaXPrime = XphyPrime / (0.0042*d); else if (d >= 11.5 && d < 21.5) DeltaXPrime = XphyPrime / (0.00425*(d-11.5)+0.048); else if (d >= 21.5 && d < 31.5) DeltaXPrime = XphyPrime / (0.004*(d-21.5)+0.091); else if (d >= 31.5 && d < 41.5) DeltaXPrime = XphyPrime / (0.0039*(d-31.5)+0.131); else if (d >= 41.5 && d < 51.5) DeltaXPrime = XphyPrime / (0.004*(d-41.5)+0.17); else if (d >= 51.5 && d < 61.5) DeltaXPrime = XphyPrime / (0.0042*(d-51.5)+0.21); else //d >= 61.5 && d < 71.5) DeltaXPrime = XphyPrime / (0.004*(d-61.5)+0.252);
Fig. 6.54 Object acquisition and tracking along the X-axis
Peripherals
6.2
Pixy Camera
247
PS = 1.59*(DeltaXPrime + X0) + 508; Pan_Servo = PS; for (j = 0; j < 5000; j = j + 1) // j < 1000 produces lots of jerkiness; j < 2000 to 4000 still some OC1RS = Pan_Servo; WAIT(); // Wait for the servo arm to settle goto ACQUIRE; } else // 150 < X < 170 { OC1RS = Pan_Servo; goto ACQUIRE; } } else { i = i + 1; goto SCAN; } }
Fig. 6.54 (continued)
4 in Fig. 6.53. The program also calculates Theta, Θ, and DeltaTheta, ΔΘ, in reference to STEP 5 and STEP 6 of the algorithm. XphyPrime, Xphy’, and DeltaXPrime, ΔX’, are calculated according to STEP 7 and STEP 8, respectively. Finally, the program calculates the duty cycle value for the pan servo with the statement, PS = 1.59*(DeltaXPrime + X0) + 508. This value is ultimately used in the Output Compare register, OC1RS, to move the pan servo arm. Once the servo arm rotates towards the new object location, the user program returns to the acquisition mode. Now, we are ready to develop an algorithm to track the ball movements in a threedimensional space. The object acquision and tracking algorithm presented in Fig. 6.53 and Program 6.3 can be expanded to include the object movements both in the X and Y-axes (or on the X-Z and Y-Z planes) according to the new flowchart in Fig. 6.55. As we mentioned earlier, this flowchart will require two separate experiments: one experiment to establish the relationship between Xphy and ΔX as shown in Fig. 6.50, and the other between Yphy and ΔY. The new algorithm also brings forth the definition of a new angle on the YZ-plane, Φ, and the corresponding deviation angle, ΔΦ, to track the object movements along the Y-axis (or on the Y-Z plane) according to STEPS 5 and 6 in Fig. 6.55. After calculating Yphy’, ΔY’ and Y’ in STEPS 7 to 9, the pan and tilt servo arms can be simultaneously rotated, pointing the camera at the new object location.
248
6 STEP 1 Acq X, Y, H 150 < X < 170 90 < Y < 110 X, Y else STEP 2 d = 0.22 +
STEP 3
855.3 H
ΔX = X - 159 ΔY = Y - 99
STEP 4 Xphy = f(ΔX, d) Yphy = f(ΔY, d) STEP 5
STEP 6
STEP 7
STEP 8
STEP 9
= arcsin (Xphy/d) = arcsin (Yphy/d)
Δ Δ
=Δ =Δ
+ +
Xphy’ = d sinΔ Yphy’ = d sinΔ
ΔX’ = f(Xphy’, d) ΔY’ = f(Yphy’, d)
X’ = ΔX’ + 159 Y’ = ΔY’ + 99 PS = f(ΔX’ + 159) TS = f(ΔY’ + 99)
STEP 10 WAIT for the pan and tilt servos
Fig. 6.55 Flowchart to acquire and track an object along the X and Y-axes
Peripherals
6.3
Microphone
6.3
249
Microphone
Microphones, MEMS-type or condenser-type, are basically capacitors with two flexible membranes that vibrate in the presence of sound. When the microphone is biased with a resistor, R, and power supply voltage, VCC, its output, VOUT, reaches a stable value equal to VCC within a time constant, RC, as shown on the left side of Fig. 6.56. The equivalent capacitance of the microphone membrane, C, is equal to εA/d, where A is the area of the membrane, ε is the dielectic constant of air, and d is the distance between the two microphone membranes. Therefore, when sound reaches the microphone, membranes get closer to each other, reducing d. This increases the capacitance, allows current flow through R, and therefore decreases the voltage level at VOUT as shown in the middle figure. When the membranes pull away from each other, the increase in d decreases the capacitance, and increases VOUT as shown on the right side of the figure. Therefore, sound waves are translated into electrical signals at VOUT. VCC
VOUT
VCC
VOUT
VCC
R
R
R t
VOUT
t
VOUT d sound
VOUT
t
VOUT d
C=
A d
sound
VOUT
C=
A d
VOUT
d
Fig. 6.56 Operation principle of a microphone
The Digilent peripheral module, pmodMIC3 [5], houses a Knowles MEMS microphone, SPA2410LR5H [6], and a 12-bit Analog Devices ADC, ADCS7478. This unit has also a SPI interface that constantly delivers 16-bit sound data packets from the Master-In-Slave-Out (MISO) terminal, equivalent of an SDO port. Since this device is dedicated only to output sound, it does not have any Master-Out-SlaveIn (MOSI) or SDI input. The bus master is responsible to generate the clock, SCK, to retrieve data packets from the device when the device is enabled by the active-low slave-select, SS, input as shown in Fig. 6.57. Fig. 6.57 Microphone SPI output
SS
SCK
MOSI
0 0 0 0 D D D D D D D D D D D D Leading zeros
12-bit Data
250
6
Peripherals
Program 6.4 accesses the Digilent MEMS microphone to retrieve eight data packets, then stops at the instruction, breakpoint = 1. This is an artificially embedded breakpoint in the program to examine the contents of each sound data, Word [i]. Since the microphone’s SPI output delivers only 12 bits preceded by four leading zeros, the SPI_Receive function is shifted four bits to the left to reconstruct the 16-bit sound data. This program uses dspic33fj128mc802’s larger sibling, dspic33fj128mc804, which houses the DAC unit. The reason for this change is to be able to have a platform that plays back the sound data stored in the data memory right after the recording is finished. Program 6.4 // dspic33fj128mc804 is in auto sampling mode, recording from Digilent microphone #include // Configuration Register - FOSCSEL #pragma config IESO = 0 // start with a user-selected oscillator at reset #pragma config FNOSC = 7
// select FRC oscillator with Divide-N at reset
// Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3
// primary oscillators are disabled
// Configure In-Circuit Programmer, WDT #pragma config ICS = 3 // Program through PGEC1/PGED1 ports #pragma config FWDTEN = 0 // WDT is disabled #define PPSUnLock
__builtin_write_OSCCONL(OSCCON & 0xBF)
#define PPSLock
__builtin_write_OSCCONL(OSCCON | 0x40)
unsigned int SPI_Receive () { while(SPI1STATbits.SPITBF == 1); SPI1BUF = 0x0000;
// Wait until the TX buffer is empty // When empty send the junk word (0x0000) to TX buffer
while(SPI1STATbits.SPIRBF == 0);
// As junk bits shifts out valid bits enter from SDI
return SPI1BUF;
// Wait until the RX buffer is full of valid data // When full, read the valid data in SPI1BUF
} void main () { // OSCTUN Register OSCTUNbits.TUN = 0;
// select FRC = 7.37MHz
6.3
Microphone
// CLKDIV Register CLKDIVbits.FRCDIV = 3;
251
// FRCDIVN = FRC/8 = 7.37/8 = 921.25KHz = FOSC // and FP = FOSC/2 = 460.62KHz
CLKDIVbits.DOZE = 0; CLKDIVbits.DOZEN = 1;
// FP = 460.25KHz = FCY // DOZE defines the ratio between cpu and peripheral clocks
// SPI configuration MASTER mode sending 16 bits SPI1CON1bits.DISSCK = 0; SPI1CON1bits.DISSDO = 0;
// Enable the internal SPI clock // Enable the SPI data output, SDO
SPI1CON1bits.MODE16 = 1; // Enable the 16-bit data mode SPI1CON1bits.SSEN = 0; SPI1CON1bits.MSTEN = 1;
// This unit is not a slave so Slave Select pin is not used // Enable MASTER mode
SPI1CON1bits.SMP = 0; SPI1CON1bits.CKE = 1;
// Sample input at the pos edge of SCK // Output data changes when SCK goes from ACTIVE to IDLE state
SPI1CON1bits.CKP = 0; SPI1CON1bits.PPRE = 1;
// IDLE is low phase and ACTIVE is high phase of SCK // Primary SPI clock = 460/16 = 28.75KHz
SPI1CON1bits.SPRE = 7;
// Secondary SPI clock = 28.75/1 = 29KHz = SCK1
SPI1STATbits.SPIROV = 0; SPI1STATbits.SPIEN = 1;
// Clear initial overflow bit in SPI1BUF // Enable the SPI interface
// Peripheral Pin Select with RP pins PPSUnLock; RPOR4bits.RP8R = 8;
// RP8 is for SCK1 (output))
RPINR20bits.SCK1R = 8;
// RP8 is for SCK1 (input))
RPINR20bits.SDI1R = 9;
// RP9 is for SDI1
RPOR3bits.RP6R = 0;
// RP6 is a default pin to be driven manually to generate SS1
PPSLock; // Define digital I/O TRISBbits.TRISB6 = 0;
// Configure RB6 as an output for SS
TRISBbits.TRISB8 = 0; TRISBbits.TRISB9 = 1;
// Configure RB8 as an output for SCK1 // Configure RB9 as an input for SDI1
unsigned int i, Delay, breakpoint; unsigned int Word[8]; for (i = 0; i < 8; i++)
// Store 8 16-bit words
{ LATBbits.LATB6 = 0;
// Lower SS for object data reception
Word[i] = (SPI_Receive() = 5000) i = 0; // keep looping the voice data after i =5000 } } }
Fig. 6.65 Application program to play back 5000 packets of sound data
Projects 1. Implement Program 6.1 where ADXL362 3-axis accelerometer continuously reads data in default mode. Measure the tilt angle of the accelerometer, and compare with what the device produces. 2. Implement the same program in question 1 but operate ADXL362 in linked mode. 3. Implement the same program in question 1 but operate ADXL362 in loop mode. 4. Place ADXL362 3-axis accelerometer on a platform where angles can be measured. Attach nine LEDs to nine available digital RB pins as outputs from dspic33fj128mc802. Write a program such that the first LED turns on if the accelerometer is tilted between 0 and 10 degrees; the first and second LEDs turn on if the tilt angle is between 10 and 20 degrees; the first, second and third LEDs turn on if the angle is between 20 and 30 degrees, and so on. 5. Implement the CMU Pixy camera in Program 6.2 for a fixed object. Move the object to different locations, including the boundaries of the 320×200 camera screen, and verify the actual measurements against what the camera produces.
264
6
Peripherals
6. Repeat the experiment in question 5, but with the pan and tilt servos attached to the camera. Change the location of the object, and check if the pan and tilt servos rotate the camera to directly face the object. 7. Use the operational amplifier (LM386) circuit below to amplify human voice. Connect the output of this circuit to the analog input port, AN0, and use the onboard ADC in dspic33fj128mc804 to store 16-bit unsigned data. VPOS 1.65V VCC1 = 9V
RM
CPOW
M
VOUT 1.65V t
R1
C1
3.3V
t
VCC2 = 3.3V
VCC1 = 9V OUT to AN0
R1
VMIC 2V
RM = 10KΩ R1 = 56KΩ R2 = 1.5KΩ R3 = 100KΩ CPOW = 10μF C1 = 10μF C2 = 10μF
R3 t R2 C2
8. Repeat the same experiment in question 7, but this time use the onboard DAC and an operational amplifer (LM386) to playback the stored data. R1 = 10KΩ R2 = 10Ω C1 = 0.1µF C2 = 10µF C3 = 10µF C4 = 0.1µF C5 = 330µF
VCC1 = 9V C2 C1 2
6 1 LM386
DAC output R1
8 5
C4
7
3 4
C5
C3
R2
References 1. Analog Devices ADXL362 datasheet, https://www.analog.com/media/en/technical-documenta tion/data-sheets/adxl362.pdf, Accessed January 2023. 2. dspic33fj128mc802/804 Microchip processor datasheet, https://www.microchip.com/en-us/ product/dsPIC33FJ128MC802, Accessed January 2023.
References
265
3. MPLABX software design environment, https://www.microchip.com/en-us/tools-resources/ develop/mplab-x-ide, Accessed January 2023. 4. Carnegie Mellon University (CMU) Pixy camera datasheet, https://pixycam.com/pixy-cmucam5/ , Accessed January 2023. 5. Digilent peripheral module: pmodMIC3 datasheet, https://digilent.com/reference/pmod/ pmodmic3/reference-manual, Accessed January 2023. 6. Knowles MEMS SPA2410LR5H microphone datasheet, https://www.mouser.com/ datasheet/2/218/know_s_a0001418731_1-2271689.pdf, Accessed January 2023.
Chapter 7
Output Devices
Servo motors, stepping motors, brushed or brushless motors are major output devices for dspic33fj128mc802 [1]. When sensor data are collected by the microcontroller, the user program may engage one or multiple output devices based on the sensor data. All Direct Current (DC) motors use a form of Pulse Width Modulation (PWM) to operate. This chapter will demonstrate dspic33fj128mc802’s capabilities to generate synchronized PWM pulses for brushed and brushless motors, servo motors and stepper motors, using its dedicated PWM1 and PWM2 units. The microcontroller can also engage the onboard timers and the Output Compare unit to produce additional PWM signals. With the combination of six PWM signals from the PWM1 unit, two from the PWM2 unit, and four from the Output Compare unit, dspic33fj128mc802 can generate a total of 12 independent PWM signals. Both the PWM1 and PWM2 units have dedicated ports. The outputs of the Output Compare unit have to be routed to digital I/O ports, using the PPS feature in dspic33fj128mc802.
7.1
Timers
The dspic33fj128mc802 IC contains five 16-bit independent timers. This unit forms the basis to generate PWM pulses both from the PWM and the Output Compare units. A simplified block diagram of a timer is shown in Fig. 7.1
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 A. Bindal, Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers, https://doi.org/10.1007/978-3-031-27841-9_7
267
268
7
Output Devices
TMRx period
Tclock
COMPARE
PRx TMRx counter TMRx Ext clock
1
TMRx reset PRESCALE
Peripheral clock (TP)
Equal
0
TMRx clock = PRE*Tclock
clock period division clock selection
Fig. 7.1 Simplified Timer block diagram
In this block diagram, a predefined time period, PRx, is programmed into the 16-bit TMRx period register. After reset, an independent counter, TMRx, starts incrementing. When the output of the counter reaches the programmed value, PRx, in the TMRx period register, the Compare block generates the Equal ¼ 1 output, and resets the counter to repeat the process. Therefore, the counter always produces a sawtooth-shaped output in digital scale that changes between 0x0000 and a predefined 16-bit value equal to PRx. The clock signal for the timer, TMRx clock, is derived either from an external clock, or the peripheral clock whose time period, TP, is multiplied by the value programmed in the Pre-scale unit. A more detailed block diagram for a general purpose timer in dspic33fj128mc802 (Timer1) is shown in Fig. 7.2. In this architecture, the timer counter is able to receive clock either from a pre-scaled peripheral clock, or a pre-scaled external clock, or an external clock gated with the peripheral clock. This block diagram also reveals the generation of an interrupt signal, Set TxIF flag, based on a synchronization event from the gate synchronization unit, or when the compare unit produces the Equal ¼ 1 signal at its output. The other timers, Timer2 to Timer5, do not have TSYNC signal because they do not contain any synchronization unit, SYNC. Falling Edge Detect TMRx period
TMRx counter TMRx External clock
PRESCALE
00
TCKPS[1:0] 0 X1 PRESCALE
TCKPS[1:0]
SYNC
1
TSYNC
Fig. 7.2 Detailed block diagram of Timer1
TGATE TCS
Peripheral clock (TP)
TMRx clock
1
COMPARE
PRx
10
Set TxIF flag Equal
TMRx reset
0
TGATE
GATE SYNC
7.1
Timers
269
The contents of the control registers responsible for configuring and operating any of the five timers are shown in Fig. 7.3. In this figure, the Timer On bit, TON, turns on the timer, and resets its output to 0x0000. The Stop in Idle mode bit, TSIDL, discontinues the timer operation when the device enters the idle mode. The Gated Time bit, TGATE, enables the gated time accumulation feature of the timer when TGATE ¼ 1. The Clock Pre-scale bits, TCKPS[1:0], either multiplies the clock period by powers of eight, or does not multiply at all as shown in Table 7.1. The Synchronization bit, TSYNC, which applies only to Timer1, synchronizes with an external clock before it produces a clock signal for the timer if an external clock option is selected. The 32-bit timer mode bit, T32, in Timer2 and Timer4 registers combines the contents of Timer2 and Timer3 and the contents of Timer4 and Timer5 to form 32-bit timers. The Clock Source bit, TCS, selects the clock source for the timer. TCS ¼ 1 selects an external clock source. Otherwise, the internal oscillator signal at FP is applied to the timer.
15
14
13
TON
-
TSIDL
12 11 10 9 -
-
-
-
8
7
6
-
-
TGATE
5
4
3
2
1
0
-
TSYNC
TCS
-
Timer 1 control register
TCKPS[1:0]
15
14
13
TON
-
TSIDL
12 11 10 9 -
-
-
-
8
7
6
-
-
TGATE
5
4
3
2
1
0
T32
-
TCS
-
Timer 2 or 4 control register
TCKPS[1:0]
15
14
13
TON
-
TSIDL
12 11 10 9 -
-
-
-
8
7
6
-
-
TGATE
5
4
3
2
1
0
-
-
TCS
-
Timer 3 or 5 control register
TCKPS[1:0]
Fig. 7.3 Control registers of five timers in dspic33fj128mc802 Table 7.1 TCKPS[1:0] entry in the timer registers TCKPS[1:0] entry (timer1 input clock prescale) in T1CON register 11 = prescale is 1:256 10 = prescale is 1:64 01 = prescale is 1:8 00 = prescale is 1:1
270
7.2
7
Output Devices
PWM1 Unit
The PWM1 unit in dspic33fj128mc802 is able to generate PWM pulses to control a variety of DC motors, including brushed or brushless motors, servo motors or stepper motors. The basic PWM signal is shown in Fig. 7.4. In this figure, the timer clock period is determined by multiplying the peripheral clock period, TP, by a pre-scale factor, PRE. The combination of multiple timer clock periods defines the time interval for Duty Cycle, TDC, and the PWM period, TPWM. The length of the TDC period rotates the servo arm to the left or right. A complete 90o rotation to the left from a center position requires approximately TDC ¼ 1ms. Similarly, a complete 90o rotation to the right requires TDC ¼ 2ms. Although most off-the-shelf servos limit this rotation angle between 45o and +45o from the neutral position. For an analog-type servo, TPWM becomes 20ms. The digital servo requires only about 2.5ms. PRE * TP
TMR clock
PWM TDC TPWM (20ms for analog, 2.5ms for digital servo)
Fig. 7.4 PWM signal in relation to timer clock
PWM1 generates three high-polarity and three low-polarity pulses from six dedicated output ports as shown in Fig. 7.5. In general terms, the low polarity pulse is an inverted version of the high-polarity pulse. Once defined, both pulses have the same PWM period. The active state of the PWM pulse from H1 to H3 outputs corresponds to the high phase of the pulse. In contrast, the active phase of the PWM pulse from L1 to L3 outputs refers to the low phase of the pulse. Active PWM H1-H3 outputs
Inactive
Duty cycle
PWM L1-L3 outputs PWM period Fig. 7.5 Definition of high and low-polarity outputs from the PWM1 unit
7.2
PWM1 Unit
271
The duty cycle of a high-polarity pulse from an Hx (Hx ¼ H1, H2 or H3) output can be adjusted in relation to the duty cycle of a low-polarity pulse from a corresponding Lx (Lx ¼ L1, L2 or L3) output as shown in Fig. 7.6. The time interval between the falling edge of a low-polarity pulse and the rising edge of a high-polarity pulse is called the Dead Time (DTS) Active interval. Similarly, the time interval between the falling edge of a high-polarity pulse and the rising edge of a low-polarity pulse is called the Dead Time (DTS) Inactive interval.
PWM H1-H3 outputs
Inactive
Inactive
Active
Inactive
Inactive
Active
PWM L1-L3 outputs DTS Active
DTS Inactive
Fig. 7.6 Definition of dead time between high and low-polarity outputs
The simplified PWM1 architecture is shown in Fig. 7.7. In this architecture, there are basically eleven registers all connected to the 16-bit data bus. The two control registers, PWM1CON1 and PWM1CON2, configure the shape of each high and low-polarity pulse. The register, P1TCON, adjusts the time-base of each pulse from all six outputs. The dead time registers, P1DTCON1 and P1DTCON2, fine-tune the dead time between each high-polarity and low-polarity pulse. The three duty cycle registers, P1DC1, P1DC2 and P1DC3, define the duty cycle for each high/low-polarity pulse pair. The P1TPER register defines the PWM period, which is the maximum limit for the P1TMR counter. The PRESCALE register multiplies the register contents with Tclock that either originates from the peripheral clock, or from an external clock for Timer1, P1TMR. The post-scale register multiplies the PWM period at the output of P1TMR to generate an interrupt for PWM1, PWM1IF. Once the register values are stored in all eleven registers, the P1TMR starts counting up from zero at each positive edge of the selected clock. Its output is compared against P1TPER at every clock cycle. As soon as the output of P1TMR reaches the P1TPER value, the comparator resets the P1TMR counter, which defines the PWM period. After reset, the up-counter starts incrementing from zero again towards P1TPER to generate the next PWM period. Within the PWM period, the output of P1TMR is also compared against the values of three duty cycle registers, P1DC1, P1DC2 and P1DC3. Any time P1TMR output reaches a duty cycle value, the corresponding comparator informs the output driver logic to terminate the high phase of H1, H2 or H3 outputs. The L1, L2 and L3 outputs are generated similarly, but in opposite polarities as shown in Fig. 7.5.
272
7 P1TPER COMP
Output Devices
Reset
P1TMR (1, 4, 16, 64) Tclock
PRESCALE
PRE*Tclock
CLOCK CONTROL
PTCKPS
PMOD1, PMOD2, PMOD3 PEN3H, PEN2H, PEN1H PEN3L, PEN2L, PEN1L
P1TMR clock = PRE*Tclock PTMOD[1:0]
PTEN COMP
PWM1H3
P1DC1
PWM1L3
COMP
Data Bus
P1DC2
Deadtime Control & Output Driver Logic
16
PWM1H2 PWM1L2
PWM1H1 PWM1L1
COMP P1DC3
(1, 2 …16) COMP
P1TCON 0
POSTSCALE
Interrupt Control
PWM1IF
PTOPS
PWM1CON1 PTMOD[1:0] PWM1CON2
P1DTCON1
P1DTCON2
Fig. 7.7 Simplified block diagram of the PWM1 unit
A better description of PWM waveform generation is shown in Fig. 7.8. In this figure, the PWM period is stored in the P1TPER register. Three different duty cycle values are stored in the P1DC1, P1DC2 and P1DC3 registers to obtain three different high-polarity and low-polarity PWM pulse pairs (the low polarity pulses are not shown in Fig. 7.8 for clarity).
7.2
PWM1 Unit
273
PWM period
Duty cycle3
Duty cycle2 Duty cycle1
time PWM1H1 output
time Duty cycle1 PWM1H2 output
time Duty cycle2 PWM1H3 output
time Duty cycle3 PWM period
Fig. 7.8 Duty cycle and PWM period generations in PWM1 unit
274
7.3
7
Output Devices
PWM2 Unit
The simplified block diagram of the PWM2 unit is shown in Fig. 7.9. In this figure, there are eight registers connected to the 16-bit data bus as opposed to eleven registers in PWM1. There are two control registers, PWM2CON1 and PWM2CON2, configuring the high-polarity PWM2H1 and the low-polarity PWM2L1 outputs. The P2TCON register adjusts the time-base of the PWM2H1 and PWM2L1 outputs. The dead time registers, P1DTCON1 and P1DTCON2, control the dead time between the high-polarity and the low-polarity outputs from the PWM2 unit. The duty cycle register, P2DC1, defines the duty cycle for the PWM2H1 and PWM2L1 outputs. The P2TPER register stores the PWM period. The PRESCALE register multiplies Tclock with 1, 4, 16 or 64 to determine the clock frequency used in Timer2, P2TMR. Similar to the P1TMR operation, once the P2TMR starts incrementing, its output is compared against P2TPER in every clock cycle. As soon as the output of P2TMR becomes equal to P2TPER, the comparator resets the P2TMR that defines the PWM period. Within the PWM period, the output of P2TMR is also compared against the value in P2DC1. The moment the P2TMR output reaches the duty cycle stored in P2DC1 the comparator terminates the high-phase of the PWM2H1 signal as shown in Fig. 7.10. The PWM2L1 is generated similarly but in opposite polarity as shown in the same figure.
P2TPER COMP
Reset
P2TMR (1, 4, 16, 64) Tclock
PRESCALE
PRE*Tclock
CLOCK CONTROL
Data Bus
PTCKPS
P1TMR clock = PRE*Tclock
PTMOD[1:0]
16
PTEN COMP P2DC1
P2TCON
PWM2CON1
PWM2CON2
P2DTCON1
P2DTCON2
Fig. 7.9 Simplified block diagram of the PWM2 unit
Deadtime Control & Output Driver Logic
PWM2H1 PWM2L1
7.4
PWM Registers
275
PWM period
Duty cycle
time PWM2H1 output
time PWM2L1 output
time Duty cycle PWM period
Fig. 7.10 Duty cycle and PWM period generations in PWM2 unit
7.4
PWM Registers
The PWM period registers are shown in Fig. 7.11. The first register in this figure is a 15-bit register which holds the timer period, PxTPER, or the maximum value PxTMR can attain (x ¼ 1 or 2). The next register is also a 15-bit register, and it is used to store the dynamic values of PxTMR as the timer increments (or decrements). The most significant bit of this register is the direction bit, PTDIR, which shows the count direction in PxTMR.
276
7 15 14 13 12 11 10 9
8
7
6
5
4
3
2
1
Output Devices
0
PxTPER[14:0] 15
14 13 12 11 10 9
8
7
6
5
4
3
2
1
0
PTDIR PxTMR[14:0]
Fig. 7.11 PWM period register, PxTPER, and PWM Time base register, PxTMR
The registers in Fig. 7.12 are the 16-bit P1DC1, P1DC2 and P1DC3 Duty Cycle registers in PWM1, and the 16-bit P2DC1 Duty Cycle register in PWM2.
Fig. 7.12 PWM1 and PWM2 duty cycle registers
15 14 13 12 11 10 9
8
7
6
5
4
3
2
1
0
5
4
3
2
1
0
5
4
3
2
1
0
5
4
3
2
1
0
P1DC1[15:0] 15 14 13 12 11 10 9
8
7
6
P1DC2[15:0] 15 14 13 12 11 10 9
8
7
6
P1DC3[15:0] 15 14 13 12 11 10 9
8
7
6
P2DC1[15:0]
7.4
PWM Registers
277
Because a Duty Cycle register can accommodate 16 bits while the Period register is limited only to 15 bits, the resolution in Duty Cycle registers is twice as much as the resolution in the Period register. This means that a number, X, stored in the Period register is equivalent to the X number of peripheral clock periods (TP), and yields a PWM period of X*TP. On the other hand, the same number, X, stored in a Duty Cycle register produces a duty cycle duration of (X*TP)/2 as shown in Fig. 7.13. For example, if the peripheral clock operates at 100KHz (TP ¼ 10μs) and PxTPER ¼ 1000, this generates a PWM period of 10ms. However, for TP ¼ 10μs and PxDCy ¼ 1000, a duty cycle of only 5ms is obtained. 1
50
1000
FP clock
PxTPER Value
1
PxDCy Value
1
50
2
99
1000
100
1999
1001
2001
2000
PWM
Fig. 7.13 PWM1 and PWM2 duty cycle registers
Figure 7.14 shows the contents of the first PWM control register, PWMxCON1 (x ¼ 1 or 2). The upper three bits of this register, PMOD3, PMOD2 and PMOD1, show the PWM mode of operation. Logic 0 entry in PMOD bit initiates complementary mode of operation, or produces a case where the high and low-polarity PWM outputs are in complement with each other as shown at the top part of Fig. 7.15. In this case, there is only one duty cycle for both the high and the low-polarity outputs, and the high-polarity output becomes the inverted form of the low-polarity output. Logic 1 entry in any of the PMOD bits makes the high and the low-polarity outputs operate independently, each having a different duty cycle as shown in the same figure. 15 14 13 12 11 -
-
-
-
-
10
9
8
PMOD3 PMOD2 PMOD1
7
6
5
4
3
2
1
-
PEN3H
PEN2H
PEN1H
-
PEN3L
PEN2L
0 PEN1L
Fig. 7.14 PWM Control1 register, PWMxCON1 (x ¼ 1 for PWM1 and x ¼ 2 for PWM2)
278
7 PWM Hx output
Output Devices
COMPLEMENTARY MODE
time PWM Lx output
time Duty cycle PWM period
PWM Hx output
INDEPENDENT MODE
time Duty cycle 1 PWM Lx output
time Duty cycle 2 PWM period
Fig. 7.15 PWM modes of operation (x ¼ 1, 2, 3 for PWM1 and x ¼ 1 for PWM2)
The high-polarity output enable bits, PEN3H, PEN2H and PEN1H, enable PWM1H1, PWM1H2 and PWMH3 output pins from PWM1, respectively. In PWM2, there is only one high-polarity output enable pin, PEN1H, to activate the single PWM2H1 output. Similarly, the low output enable bits, PEN3L, PEN2L and PEN1L, enable PWM1L1, PWM1L2 and PWML3 output pins, respectively. Since there is one PWM2L1 low output that emerges from PWM2, the only low output enable pin in this control register is PEN1L.
7.4
PWM Registers
279
The second PWM control register, PWMxCON2, (x ¼ 1 or 2) is shown in Fig. 7.16. This register has four basic entries for special events. The Special Event Output Post-scale entry, SEVOPS[3:0], multiplies the PWM period with integers between 1 and 16 to create longer periods to trigger a special event as shown in Table 7.2. The Immediate Update Enable bit, IUE, enables the duty cycle register updates immediately when set, or enables the updates to be synchronized with the PWM time base. The Override Synchronization bit, OSYNC, syncs output overrides with PWM time-base when set; otherwise, output overrides occur at the next TP boundary. The PWM Update Disable bit, UDIS, disables any updates from the period or duty cycle registers when set. 15 14 13 12 11 10 -
-
-
9
8
-
7
6
5
4
3
2
1
0
-
-
-
-
-
IUE
OSYNC
UDIS
SEVOPS[3:0]
Fig. 7.16 PWM Control2 register, PWMxCON2 (x ¼ 1 for PWM1 and x ¼ 2 for PWM2)
Table 7.2 SEVOPS[3:0] entry in PWMxCON2 (x ¼ 1 for PWM1 and x ¼ 2 for PWM2) SEVOPS[3:0] entry (special event trigger output postscale) in PWMxCON2 register 1111 = 1:16 postscale 1110 = 1:15 postscale ... 1111 = 1:3 postscale 1111 = 1:2 postscale 0000 = 1:1 postscale
The time base control register, PxTCON, (x ¼ 1 or 2) shown in Fig. 7.17 controls the time-base of the PWM signal. In this register, the Timer Enable bit, PTEN, enables the PxTMR timer. The timer starts counting up towards the 15-bit PxTPER value before it receives a reset. The Time-base Stop in Idle bit, PTSIDL, halts counting in PxTMR when set. The Time-base Output Post-scale bits, PTOPS[3:0], multiply the PWM period according to Table 7.3 in order to generate a PWM-based interrupt as shown in Fig. 7.7. The interrupt signal can be generated either at the end of each PWM period, or once in every 16 PWM periods.
280
7 15
14
13
PTEN
-
PTSIDL
12 11 10 9 -
-
-
8
-
7
6
5
4
3
2
Output Devices 1
0
-
PTOPS[3:0]
PTMOD[1:0]
PTCKPS[1:0]
Fig. 7.17 Time-base control register, PxTCON (x ¼ 1 for PWM1 and x ¼ 2 for PWM2)
Table 7.3 Time-base Output Post-scale entry, PTOPS[3:0], in PxTCON register PTOPS[3:0] entry (time base output postscale) in PxTCON register 1111 = 1:16 postscale 1110 = 1:15 postscale ... 1111 = 1:3 postscale 1111 = 1:2 postscale 0000 = 1:1 postscale
The Time-base Input Clock Pre-scale bits, PTCKPS[1:0], multiplies the clock period for the PWM1 and PWM2 timers to achieve faster or slower counting operation. The multiplication factor changes with powers of four as tabulated in Table 7.4. Table 7.4 Time-base Input Pre-scale entry, PTCKPS[1:0], in PxTCON register PTCKPS[1:0] entry (time base input clock prescale) in PxTCON register 11 = clock period is 64*TP (1:64 prescale) 10 = clock period is 16*TP (1:16 prescale) 01 = clock period is 4*TP (1:4 prescale) 00 = clock period is TP (1:1 prescale)
Finally, the Time-base Mode Selection entry, PTMOD[1:0], determines the PWM time-base mode. PWM1 or PWM2 can be operated either in free-running mode, continuous up/down count mode, or a single pulse mode as shown in Table 7.5.
7.4
PWM Registers
281
Table 7.5 Time-base Mode entry, PTMOD[1:0], in PxTCON register PTMOD[1:0] entry (time base mode) in PxTCON register 11 = timebase operates in a continuous up/down count mode with interrupts 10 = timebase operates in a continuous up/down count mode 01 = timebase operates in a single pulse mode 00 = timebase operates in a free-running mode
A better description of the continuous up/down and free-running modes are given in Fig. 7.18. In this figure, the value in the PxTPER register doubles the PWM period in the continuous up/down mode with respect to the PWM period in the free-running mode. The same applies to the duty cycle. The apparent duty cycle period in the up/ down mode becomes twice as big as the duty cycle in the free-running mode. There is also an option in the PTMOD[1:0] entry that can generate interrupts. The single pulse mode refers to generating only a single pulse from the corresponding highpolarity output (and from the low-polarity output if it is enabled). Fig. 7.18 Continuous up/down and free running PMW modes
CONTINUOUS UP/DOWN MODE PxTPER
PxDC
time PWM Hx output
time 2*Duty cycle 2*PWM period
FREE-RUNNING MODE PxTPER
PxDC
time PWM Hx output
time Duty cycle PWM period
282
7
Output Devices
Dead time is defined as the non-overlapping time interval between high and low-polarity PWM signals, and it is a crucial element for brushless DC motor operation. The dead time interval and its point of origination are determined by two dead time registers. The first dead time control register in Fig. 7.19 defines the dead time interval between the high and the low-polarity PWM outputs if complementary mode is engaged as shown in Fig. 7.20. 15 14 13 12 11 10 9
DTBPS[1:0]
8
DTB[5:0]
7
6
5
DTAPS[1:0]
4
3
2
1
0
DTA[5:0]
Fig. 7.19 Dead time1 register, PxDTCON1 (x ¼ 1 for PWM1 and x ¼ 2 for PWM2)
PWM Hx output
PWM Lx output Dead time
Dead time
Fig. 7.20 Definition of dead time period in the PxDTCON1 register
The Dead time unit B Pre-scale entry, DTBPS[1:0], defines the clock period for dead time unit B, and ranges between the peripheral clock period, TP, and 8*TP according to Table 7.6. The actual Dead time period for unit B stems from DTB[5:0], and yields a time interval of N*TP*DTB where N is the clock multiplier used in the DTBPS entry. The clock multiplier for the A unit, DTAPS[1:0], and the dead time period for the A unit, DTA[5:0], are identical to their counterparts for the B unit. Table 7.6 The dead time entry, DTBPS[1:0] for unit B in PxDTCON1 register DTAPS[1:0] and DTBPS[1:0] entries in PxDTCON1 register 11 = clock period for deadtime unit A is 8*TCY 10 = clock period for deadtime unit A is 4*TCY 11 = clock period for deadtime unit A is 2*TCY 11 = clock period for deadtime unit A is TCY
7.5
Sample Programs for PWM Operation
283
The second dead time register, PxDTCON2, in Fig. 7.21 determines if the dead times are generated from the unit A or unit B once the dead time periods are configured in the PxDTCON1 register. The Dead time Select bit for the PWMH3 output going Active, DTS3A, refers to the DTS Active interval in Fig. 7.6. When DTS3A ¼ 0, this active interval is determined by the unit A. Similarly, the Dead time Select bit for the PWML3 output going Inactive, DTS3I, refers to the DTS Inactive interval in the same figure. When DTS3I ¼ 0, this inactive interval is determined by the unit A. The same convention applies to DTS2A, DTS2I, DTS1A and DTS1I bits for the remaining PWMH2, PWML2, PWMH1 and PWML1 outputs in this register. For PWM2, only DTS1A and DTS1I bits are valid. 15 14 13 12 11 10 9 -
-
-
-
-
-
-
8
7
6
5
4
3
2
1
0
-
-
-
DTS3A
DTS3I
DTS2A
DTS2I
DTS1A
DTS1I
Fig. 7.21 Dead time2 register, PxDTCON1 (x ¼ 1 for PWM1 and x ¼ 2 for PWM2)
7.5
Sample Programs for PWM Operation
The first PWM example is to generate a single PWM output with 1msec pulse width and 20msec period from the PWM1H1 terminal of dspic33fj128mc802. Because this terminal corresponds to the physical pin no. 25, and it is configured only as an output, neither any PPS function nor a TRIS statement is necessary in the user program. This example is given in Program 7.1 [2] which is divided in three sections: the oscillator and clock configuration, PWM register setup, and generation of the PWM signal from the dedicated port, PWM1H1. The intent of the program is only to generate a variety of PWM signals from dspic33fj128mc802, and does not contain any application section. Program 7.1
// PWM has a single HIGH output generated by motor control unit #include // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled
284
7
Output Devices
#pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3
// primary oscillators are disabled
// Configuration Register - FOSCSEL #pragma config IESO = 0 // start with a user-selected oscillator at reset #pragma config FNOSC = 7
// select FRC oscillator with postscalar at reset
// Configuration Register - Motor Control PWM FPOR #pragma config PWMPIN = 0 #pragma config HPOL = 1
// All Motor Control PWM outputs are active at Reset // Motor control HIGH outputs are active high
#pragma config LPOL = 1 // Motor control LOW outputs are active high #pragma config FWDTEN = 0 // Disable Watch Dog timer int main () { // OSCTUN Register OSCTUNbits.TUN = 0;
// select FRC = 7.37MHz
// CLKDIV Register CLKDIVbits.FRCDIV = 6; // FRCDIVN = FRC/64 = 7.37/64 = 115.15KHz = FOSC // 115.15/2 = 57.57KHz = FP (FP operates the timers) CLKDIVbits.DOZE = 1; // FOSC/4 = 115.15/4 = 28.8KHz = FCY CLKDIVbits.DOZEN = 1; // DOZE defines the ratio between cpu and peripheral clocks // PWM1 timebase control register P1TCONbits.PTEN = 0; // Do not enable the clock delivery to PWM1 timer yet P1TCONbits.PTCKPS = 0; // Prescale is 1:1 so Timer1 clock = 57.6KHz = FP P1TCONbits.PTMOD = 0; // PWM1 is in free-running mode // PWM1 counter and period P1TMRbits.PTMR = 0; P1TPER = 1200;
// Initial value in PWM1 counter register // PWM1 period register produces 20msec PWM period
// PWM1 control register1 PWM1CON1bits.PMOD3 = 1; // PWM1H3 and PWM1L3 outputs are independent mode PWM1CON1bits.PMOD2 = 1; // PWM1H2 and PWM1L2 outputs are independent mode PWM1CON1bits.PMOD1 = 1; // PWM1H1 and PWM1L1 outputs are independent mode PWM1CON1bits.PEN3H = 0; // PWM1H3 is disabled PWM1CON1bits.PEN2H = 0; // PWM1H2 is disabled PWM1CON1bits.PEN1H = 1; // PWM1H1 is enabled PWM1CON1bits.PEN3L = 0; // PWM1H3 is disabled PWM1CON1bits.PEN2L = 0; // PWM1H2 is disabled PWM1CON1bits.PEN1L = 0; // PWM1H1 is disabled
7.5
Sample Programs for PWM Operation
// PWM1 control register2 PWM1CON2bits.IUE = 0;
285
// Updates are synchronized with timebase
PWM1CON2bits.UDIS = 0; // Updates from period and duty cycle registers are enabled // PWM1 duty cycle register1 P1DC1 = 120; // Duty cycle register1 produces 1msec pulse // Enable timebase control register P1TCONbits.PTEN = 1; // Enable the clock delivery to PWM1 timer while (1); }
The first segment of Program 7.1 generates a clock signal for the PWM1 unit. This section also includes additional pragma statements, emphasizing the polarity of the PWM pulse and its continuity as shown in Fig. 7.22. There are a total of five statements that configure the system oscillator. This program intends to use a single internal FRC oscillator. Therefore, the first three FOSC statements disable the means of using any external oscillator. The first FOSC statement, #pragma config FCKSM ¼ 3, turn off the clock-switching and fail-safe modes. The second statement, #pragma config OSCIOFNC ¼ 0, disables the external oscillator pin, OSCO, and transforms this pin into a general-purpose pin. Finally, the third statement, #pragma config POSCMD ¼ 3, disables the use of any HS, XT or EC-type primary oscillators. The FOSCSEL register is another configuration register which has two related entries against using an external oscillator. The #pragma config IESO ¼ 0 statement disables any internal-to-external oscillator switch-over mechanism, and the #pragma config FNOSC ¼ 7 statement defines the internal FRC oscillator data-path with FRCDIVN output to generate the system clock. The next four pragma statements configure the two PWM units. The statement, #pragma config PWMPIN ¼ 0, configures all PWM pins to be outputs when the system resets. Otherwise, PWM pins will be controlled by the PORT register at reset. The #pragma config HPOL ¼ 1 statement makes all the high-polarity PWM outputs, such as PWM1H1, PWM1H2, PWM1H3 and PWM2H1, to produce active-high pulses. Similarly, the #pragma config LPOL ¼ 1 statement makes all the low-polarity PWM outputs, such as PWM1L1, PWM1L2, PWM1L3 and PWM2L1, to produce active-high pulses. Finally, the #pragma config FWDTEN ¼ 0 statement disables the watchdog timer to have PWM signals to be generated continuously without any interruption.
286
7
Output Devices
// PWM has a single HIGH output generated by motor control unit #include // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled // Configuration Register - FOSCSEL #pragma config IESO = 0 // start with a user-selected oscillator at reset #pragma config FNOSC = 7 // select FRC oscillator with postscalar at reset // Configuration Register - Motor Control PWM FPOR #pragma config PWMPIN = 0 // All Motor Control PWM outputs are active at Reset #pragma config HPOL = 1 // Motor control HIGH outputs are active high #pragma config LPOL = 1 // Motor control LOW outputs are active high #pragma config FWDTEN = 0 // Disable Watch Dog timer int main () { // OSCTUN Register OSCTUNbits.TUN = 0;
// select FRC = 7.37MHz
// CLKDIV Register CLKDIVbits.FRCDIV = 6; // FRCDIVN = FRC/64 = 7.37/64 = 115.15KHz = FOSC // 115.15/2 = 57.57KHz = FP (FP operates the timers) CLKDIVbits.DOZE = 1; // FOSC/4 = 115.15/4 = 28.8KHz = FCY CLKDIVbits.DOZEN = 1; // DOZE defines the ratio between cpu and peripheral clocks … }
Fig. 7.22 Configuration of PWM1 to generate a PWM waveform
To set up the oscillator frequency, we use the OSCTUN and CLDIV registers. The OSCTUNbits.TUN ¼ 0 statement tunes the internal FRC clock at 7.37MHz. The statement, CLKDIVbits.FRCDIV ¼ 6, divides the 7.37MHz FRC clock frequency by 64, and produces 115.15KHz at FOSC. The CLKDIVbits.DOZEN ¼ 1 statement enables the DOZE unit; the CLKDIVbits.DOZE ¼ 1 statement divides the clock at FOSC by four, and generates FCY ¼ 115.15/4 ¼ 28.8KHz for the system. As a result, FP becomes FP ¼ FOSC/2 ¼ 115.15.15/2 ¼ 57.57KHz, and operates the timers in the PWM1 and PWM2 units.
7.5
Sample Programs for PWM Operation
287
The second section of the program in Fig. 7.23 shows the register setup in PWM1 to generate a single waveform with a pulse width of 1msec and a period of 20msec from the PWM1H1 terminal as indicated previously. The PWM1 unit achieves this by using a time-base register, a period register and two control registers. The Time-base Control register, P1TCON, sets up the parameters in the clock generation data-path for PWM1, P1TMR. The first statement, P1TCONbits.PTEN ¼ 0, disables P1TMR until all timer parameters are programmed into the PWM1 registers. The P1TCONbits.PTCKPS ¼ 0 statement multiplies the incoming clock period, Tclock, in Fig. 7.7 by one before directing it to P1TMR. The P1TCONbits. PTMOD ¼ 0 statement ensures PWM1 to operate in free-running mode as opposed to continuous up/down mode as shown in Fig. 7.18. The PWM1 and the counter are the next two units to program. The P1TMRbits. PTMR ¼ 0 statement resets P1TMR. The P1TPER ¼ 1200 statement programs the PWM1 Time Period register, P1TPER, for the maximum value of 1200. This number corresponds to the maximum value in P1TMR, and it is equivalent to a 20 msec PWM period with the supplied clock. The statements, PWM1CON1bits.PMOD1 ¼ 1, PWM1CON1bits.PMOD2 ¼ 1 and PWM1CON1bits.PMOD3 ¼ 1, assign all high and low-polarity outputs of PWM1 to run in independent mode (as opposed to complementary mode) as shown in Fig. 7.15. The PWM1CON1bits.PEN1H ¼ 1 statement activates PWM1H1 output only, but not the other five PWM1 outputs. The PWM1CON2bits.IUE ¼ 0 statement disables all immediate updates for duty cycle registers. Instead, it synchronizes the updates with the PWM time-base. Finally, the PWM1CON2bits.UDIS ¼ 0 statement enables updates from duty cycle and time period registers as soon as they are programmed with new values. The first PWM1 Duty Cycle register, P1DC1, is the only register to be programmed among the other duty cycle registers in order to produce a single output from PWM1H1. The P1DC1 ¼ 120 corresponds to a 1 msec pulse width according to the clock period supplied to P1TMR. // PWM has a single HIGH output generated by motor control unit #include … int main () {
Fig. 7.23 PWM1 register setup to generate PWM waveform from PWM1H1 terminal
288
7
Output Devices
… // PWM1 timebase control register P1TCONbits.PTEN = 0; // Do not enable the clock delivery to PWM1 timer yet P1TCONbits.PTCKPS = 0; // Prescale is 1:1 so Timer1 clock = FP= 57.6KHz P1TCONbits.PTMOD = 0; // PWM1 is in free-running mode // PWM1 counter and period P1TMRbits.PTMR = 0; P1TPER = 1200;
// Initial value in PWM1 counter register // PWM1 period register produces 20msec PWM period
// PWM1 control register1 PWM1CON1bits.PMOD3 = 1; // PWM1H3 and PWM1L3 outputs are independent mode PWM1CON1bits.PMOD2 = 1; // PWM1H2 and PWM1L2 outputs are independent mode PWM1CON1bits.PMOD1 = 1; // PWM1H1 and PWM1L1 outputs are independent mode PWM1CON1bits.PEN3H = 0; // PWM1H3 is disabled PWM1CON1bits.PEN2H = 0; // PWM1H2 is disabled PWM1CON1bits.PEN1H = 1; // PWM1H1 is enabled PWM1CON1bits.PEN3L = 0; // PWM1H3 is disabled PWM1CON1bits.PEN2L = 0; // PWM1H2 is disabled PWM1CON1bits.PEN1L = 0; // PWM1H1 is disabled // PWM1 control register2 PWM1CON2bits.IUE = 0; // Immediately updates to duty cycle registers are disabled PWM1CON2bits.UDIS = 0; // Updates from period and duty cycle registers are enabled // PWM1 duty cycle register1 P1DC1 = 120; // Duty cycle register1 produces 1msec pulse // Enable timebase control register P1TCONbits.PTEN = 1; // Enable the clock delivery to PWM1 timer … }
Fig. 7.23 (continued)
When all vital PWM1 registers are programmed, P1TMR is turned on by the statement, P1TCONbits.PTEN ¼ 1, and PWM1 becomes operational. The third section of the user program explains how to generate a continuous PWM waveform as shown in Fig. 7.24. This is accomplished by a while loop, while (1), in conjunction with disabling the watchdog timer, #pragma config FWDTEN ¼ 0, as discussed previously. As a result, a PWM signal with a 20 msec period and 1 ms pulse width is generated from the PWM1H1 terminal without being interrupted by any other process.
7.5
Sample Programs for PWM Operation
289
// PWM has a single HIGH output generated by motor control unit #include … int main () { … while (1); }
Fig. 7.24 Continuous PWM waveform generation
The second PWM example generates eight independent PWM outputs. The first six of these PWM outputs are generated in PWM1 while the last two are generated in PWM2. The first two PWM1 outputs from the PWM1H1 and PWM1L1 ports are programmed to produce two identical waveforms, each with 1 msec pulse width in a 20 msec period. The second set from the PWM1H2 and PWM1L2 ports are programmed to produce another two identical waveforms, each with 1.5 msec pulse width and 20 msec period. Finally, the third set of waveforms from the PWM1H3 and PWM1L3 outputs are programmed to produce 2 msec pulse width and 20 msec period. Both the PWM2H1 and PWM2L1 outputs from PWM2 are programmed to generate 1 msec pulse width in a 20 msec period. The user program that generates all eight waveforms specified above is given in Fig. 7.25. The program is again divided in three sections: the oscillator configuration, PWM register setup and generation of PWM signals from the dedicated ports without any interruption. The oscillator configuration in this program is identical to the one given in Fig. 7.22, and will not be discussed again. The most important configuration statements in this segment is to select the oscillator type, determine the PWM clock frequency, and define the nature of the high and low-polarity PWM outputs. The register setup in this program is similar to the one in Fig. 7.23 although a lot more features are enabled as shown in Fig. 7.25.
290
7 Output Devices
// PWM has six HIGH outputs from PWM1 and dual high outputs from PWM2 by motor control unit #include … int main () { … // PWM1 timebase control register P1TCONbits.PTEN = 0; // Do not enable the clock delivery to PWM1 timer yet P1TCONbits.PTCKPS = 0; // Prescale is 1:1 so Timer1 clock = 57.6KHz P1TCONbits.PTMOD = 0; // PWM1 is in free-running mode // PWM1 counter and period P1TMRbits.PTMR = 0; // Initial value in PWM1 counter register P1TPER = 1200; // PWM1 period register produces 20msec PWM period // PWM1 control register1 PWM1CON1bits.PMOD3 = 1; // PWM1H3 and PWM1L3 outputs are independent mode PWM1CON1bits.PMOD2 = 1; // PWM1H2 and PWM1L2 outputs are independent mode PWM1CON1bits.PMOD1 = 1; // PWM1H1 and PWM1L1 outputs are independent mode PWM1CON1bits.PEN3H = 1; // PWM1H3 is enabled PWM1CON1bits.PEN2H = 1; // PWM1H2 is enabled PWM1CON1bits.PEN1H = 1; // PWM1H1 is enabled PWM1CON1bits.PEN3L = 1; // PWM1L3 is enabled PWM1CON1bits.PEN2L = 1; // PWM1L2 is enabled PWM1CON1bits.PEN1L = 1; // PWM1L1 is enabled // PWM1 control register2 // Updates are synchronized with timebase PWM1CON2bits.IUE = 0; PWM1CON2bits.UDIS = 0; // Updates from period and duty cycle registers are enabled // PWM1 duty cycle register1 // Duty cycle register1 produces 1msec pulse P1DC1 = 120; // PWM1 duty cycle register2 // Duty cycle register2 produces 1.5msec pulse P1DC2 = 180; // PWM1 duty cycle register3 // Duty cycle register3 produces 2msec pulse P1DC3 = 240; // PWM2 timebase control register // Do not enable the clock delivery to PWM2 timer yet P2TCONbits.PTEN = 0; P2TCONbits.PTCKPS = 0; // Prescale is 1:1 so Timer2 clock = 57.6KHz P2TCONbits.PTMOD = 0; // PWM2 is in free-running mode
Fig. 7.25 PWM1 and PWM2 register setup to enable all eight PWM outputs
7.5
Sample Programs for PWM Operation
291
// PWM2 counter and period P2TMRbits.PTMR = 0; // Initial value in PWM2 counter register P2TPER = 1200; // PWM2 period register produces 20msec PWM period // PWM2 control register1 PWM2CON1bits.PMOD1 = 1; // PWM2H1 and PWM2L1 outputs are independent mode PWM2CON1bits.PEN1H = 1; // PWM2H1 is enabled PWM2CON1bits.PEN1L = 1; // PWM2L1 is enabled // PWM2 control register2 PWM2CON2bits.IUE = 0; PWM2CON2bits.UDIS = 0;
// Updates are synchronized with timebase // Updates from period and duty cycle registers are enabled
// PWM2 duty cycle register P2DC1 = 120; // Duty cycle register2 produces 1msec pulse // Enable timebase control register // Enable the clock delivery to PWM1 timer P1TCONbits.PTEN = 1; P2TCONbits.PTEN = 1; // Enable the clock delivery to PWM2 timer … }
Fig. 7.25 (continued)
The P1TCONbits.PTEN ¼ 0 statement in P1TCON register disables P1TMR until the PWM1 and PWM2 timer parameters are programmed. The P1TCONbits. PTCKPS ¼ 0 statement multiplies the system clock period by one, and directs it to P1TMR. The P1TCONbits.PTMOD ¼ 0 statement engages the free-running mode for both timers. The P1TMRbits.PTMR ¼ 0 statement resets P1TMR. The P1TPER ¼ 1200 statement sets up the P1TPER register with a value of 1200, and prompts the PWM1 unit to generate a PWM signal with 20 msec period. The mode statements, PWM1CON1bits.PMOD1 ¼ 1, PWM1CON1bits.PMOD2 ¼ 1 and PWM1CON1bits.PMOD3 ¼ 1, assert the independent mode of operation for both timers. The statements, PWM1CON1bits.PEN1L ¼ 1 to PWM1CON1bits. PEN3H ¼ 1, enable all the low and high-polarity outputs of PWM1 to simultaneously produce PWM signals. The PWM1 Duty Cycle registers, P1DC1 ¼ 120, P1DC2 ¼ 180 and P1DC3 ¼ 240, adjust the PWM pulse widths to be 1 msec, 1.5 msec and 2 msec from the first, second and third high and low-polarity PWM1 outputs, respectively. The PWM2 registers, P2TPER and P2DC1, are programmed similarly to generate two identical PWM signals, each with 1 msec pulse width and 20 msec period. Once PWM1 and PWM2 registers are programmed, both P1TMR and P2TMR are turned on by the statements, P1TCONbits.PTEN ¼ 1 and P2TCONbits.PTEN ¼ 1, simultaneously.
292
7
Output Devices
The while statement, while (1), shown in Fig. 7.24 is the last statement in this program which ensures the generation of all eight PWM signals as shown in Fig. 7.26.
Fig. 7.26 Logic analyzer snapshot of all eight waveforms from PWM1 and PWM2
The third example generates a single PWM output from the PWM1H1 port with varying pulse widths between 1msec and 2msec. The purpose of this exercise is to rotate the servo arm between -90o and +90o (or between -45o and +45oin most offthe-shelf servos). The first segment of the program configures the oscillator and the PWM registers, using pragma statements, and it is identical to Fig. 7.22. The second segment configures the PWM1 registers, and it is identical to Fig. 7.23 because it only enables the PWM1H1 port. The third segment explains how to generate a varying PWM pulse width from PWM1H1 terminal as shown in Fig. 7.27. The delay function in this section, delay (limit1, limit2), operates based on two variables, i and j. The purpose of using two variables as opposed to using just one variable is to be able to create much longer delays if necessary. However, a delay function with a single variable may still function satisfactorily depending on the speed of the servo in use. The while (1) statement in Fig. 7.27 encompasses two duty cycle assignments for P1DC1. The first statement, P1DC1 ¼ 120, produces a 1 msec pulse width in a 20 msec period (issued earlier by programming P1TPER with 1200), and rotates the servo arm to -90o (or -45o for off-the-shelf servos) position, assuming that the previous servo arm position is at +90o (or +45o for off-the-shelf servos). The program waits for 100 instruction cycles for the servo arm to complete its rotation by the delay function, delay (10, 10). If the servo is a slower servo, the delay time should be extended to a much higher value to match the servo speed. After this delay, P1DC1 value is increased to 240, which rotates the servo arm from -90o to +90o (or from -45o to +45o for off-the-shelf servos). A second delay function, delay (10, 10), is inserted after this assignment in order to give enough time to the servo to complete its rotation.
7.6
Output Compare Unit
293
// PWM1 has a single HIGH output changing between 1msec and 2msec #include ... // delay function unsigned int limit1, limit2; void delay (limit1, limit2) { unsigned int i, j; for (i = 0; i < limit1; i++) { for (j = 0; j < limit2; j++); } } int main () { … while (1) { P1DC1 = 120; delay (10, 10); P1DC1 = 240; delay (10, 10); }
// This is to have the servo arm to swing between -90 and +90 degrees // Duty cycle register1 will produce 1msec pulse for -90 degrees // Wait for 100 cycles // Duty cycle register1 will produce 2msec pulse for +90 degrees // Wait for 100 cycles
}
Fig. 7.27 Continuous PWM waveform generation with varying pulse width
7.6
Output Compare Unit
The Output Compare (OC) unit is another unit that can generate a variety of PWM signals. In this section, we will show how to generate a total of four PWM outputs from the OC unit, using Timer2 (TMR2) or Timer3 (TMR3).
294
7
Output Devices
The OC unit operates with an up-down counter from TMR2 or TMR3, and four OC registers, OC1R to OC4R, as shown in Fig. 7.28. It can generate four independent PWM signals from its OC ports, OC1 to OC4.
Secondary Duty Cycle Regs Primary Duty Cycle Regs
OC1RS
OC2RS
OC3RS
OC4RS
OC1R
OC2R
OC3R
OC4R
TMR2
0
TMR3
1
OCTSEL
OC1
COMPARE
OC2 Output Logic OCTSEL
0
OC3
1 OCM[2:0]
OC4
OC4IF
OC3IF
OC1IF
OC2IF
TMR2 TMR3
Fig. 7.28 Simplified block diagram of the Output Compare (OC) unit
If the mode is programmed so that the unit generates only PWM signals, the selected OC counter starts counting up towards the value stored in the first OCxR register (x ¼ 1, 2, 3 or 4). The unit compares the counter contents against all active OCxR registers at every positive edge of the clock as the counting takes place. When the counter contents become equal to the value in an OCxR register, the unit stops increasing the pulse width, thus establishing the duty cycle within a given PWM period as shown in Fig. 7.29. All four waveforms in this figure can be successively generated one after the other with the values stored in the OCxR registers.
7.6
Output Compare Unit
295
End of PWM period
TMR3
OC4R OC3R OC2R OC1R t DC1 DC2 DC3 DC4
OC1
t OC2
t OC3
t OC4
t PWM period
Fig. 7.29 Waveform generations from the Output Compare unit
Fig. 7.30 shows a snapshot of four PWM signals from the OC unit, using the logic analyzer. The pulse width of the first waveform is 1 msec. This is followed by waveforms with 2 msec, 3 msec and 4 msec pulse widths. The period of each waveform is 20 msec.
296
7
Output Devices
Fig. 7.30 Four outputs with 1, 2, 3 and 4 msec pulse widths in 20 msec period from the OC unit
The Fig. 7.31 shows the 16-bit primary and secondary OCxR registers. The duty cycle is initially programmed into the secondary OCxR register, OCxRS. The duty cycle is not transferred from the OCxRS register to the primary OCxR register until the timer self-resets at the waveform period mark, or the counter contents match the programmed period value in the period register. Therefore, OCxRS register can be programmed at any time since reprogramming its contents does not interfere with the OC operation. In contrast to the 15-bit Period registers, P1TPER and P2TPER, that reside in the PWM1 and PWM2 motor control units, the Period registers, PR2 and PR3, in the Output Compare unit are both 16-bit registers whose bit field formats match the bit field formats of the Duty Cycle registers, OCxR and OCxRS. Therefore, if a number, X, is stored in a Period register in the OC unit, it produces a PWM period equal to X*TP. Similarly, an arbitrary number, Y, stored in a Duty Cycle register results in a duty cycle duration of Y*TP. Fig. 7.31 Primary and secondary OCxR registers to adjust duty cycles (x ¼ 1, 2, 3, 4)
15 14 13 12 11 10
9
8
7
6
5
4
3
2
1
0
5
4
3
2
1
0
OC Value[15:0]
15 14 13 12 11 10
9
8
7
6
OC Secondary Value[15:0]
There is only one OC control register, OCxCON, controlling the PWM waveform generation from each of the four independent OC units as shown in Fig. 7.32. The OC Stop at Idle bit, OCSIDL, in this register halts the unit operation when the CPU goes into the idle mode when set. The OC Fault condition status bit, OCFLT, detects
7.6
Output Compare Unit
297
a fault condition when it is set. The OC Timer Select bit, OCTSEL, selects Timer2 when OCTSEL ¼ 0, and Timer3 when OCTSEL ¼ 1. 15 14 -
-
13 OCSIDL
12 11 10 -
-
-
9
8
7
6
5
4
3
-
-
-
-
-
OCFLT
OCTSEL
2
1
0
OCM[2:0]
Fig. 7.32 OCxCON control register
The OC Mode entry, OCM[2:0], selects one of the eight possible OC modes as shown in Table 7.7. When OCM[2:0] ¼ 0, the OC output becomes disabled, and does not produce any waveform. The OCM[2:0] ¼ 1 entry generates an active-low one shot signal from an OC output as shown in Fig. 7.33. The OCM[2:0] ¼ 2 entry makes the one-shot waveform an active-high waveform. The OCM[2:0] ¼ 3 entry initializes the waveform at logic 0, but toggles it every time the counter reaches the OCxR value. When OCM[2:0] ¼ 4, the output waveform once again becomes an active-high one-shot pulse delayed by the duty cycle, DCx. The OCM[2:0] ¼ 5 entry converts the delayed active-high one-shot waveform into a continuous pulse form. Finally, the OCM[2:0] ¼ 6 and OCM[2:0] ¼ 7 entries produce PWM waveforms with and without disabling the fault pin, respectively. Table 7.7 OC Mode select entry, OCM[2:0], in OCxCON register OCM[2:0] entry (output compare mode select) in OCxCON register 111 = PWM mode on OCx. Fault pin is enabled 110 = PWM mode on OCx. Fault pin is disabled 101 = Initialize OCx low. Generate continuous output pulses on OCx 100 = Initialize OCx low. Generate a single output pulse on OCx 011 = Compare event toggles OCx 010 = Initialize OCx high. Compare event forces OCx low 001 = Initialize OCx low. Compare event forces OCx high 000 = OCx channel is disabled
298
7
Output Devices
End of PWM period TMR3
OCxRS OCxR t DCx Active-low one shot mode
t Active-high one shot mode
t Toggle mode
t Delayed one shot mode
t Continuous pulse mode
t PWM mode
t PWM period
Fig. 7.33 Output Compare Modes, OCM[2:0], in OCxCON control register
Program 7.2 generates four independent PWM signals with 1msec, 2 msec, 3 msec and 4 msec pulse widths in a 20 msec period. Since the PWM2 unit uses Timer2 to generate two PWM waveforms from its PWM2H1 and PWM2L1 ports, the OC unit is restricted to use only Timer3 to prevent any source conflict.
7.6
Output Compare Unit
299
Program 7.2
// Quad PWM outputs are generated by output compare unit in dspic33fj128mc802 #include // Configuration Register - FOSCSEL #pragma config IESO = 0 #pragma config FNOSC = 7
// start with a user-selected oscillator at reset // select FRC oscillator with postscalar at reset
// Configuration Register - FOSC #pragma config FCKSM = 3
// both clock switching and fail-safe modes are disabled
#pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled int main () { // OSCTUN Register OSCTUNbits.TUN=0;
// select FRC = 7.37MHz
// CLKDIV Register CLKDIVbits.FRCDIV=6; // FRCDIVN = FRC/64 = 7.37/64 = 115.15KHz = FOSC CLKDIVbits.DOZE=1; // FOSC/4 = 115.15/4 = 28.8KHz = FCY CLKDIVbits.DOZEN=1; // DOZE defines the ratio between cpu and peripheral clocks // Program Timer3 control register T3CONbits.TGATE = 0; // Gated (with external clock) timer3 clock is disabled T3CONbits.TCKPS = 0; // Prescale is 1:1 so the Timer3 clock = 57.6KHz = FP T3CONbits.TCS = 0; // Timer3 works with the internal clock = 57.6KHz = FP // Program Timer3 time base TMR3 = 0;
// Initial value in Timer3 counter register
PR3 = 1200;
// Timer3 period register produces 20msec PWM period
// Configure Output Compare1 control register to work with Timer3, OC1CON OC1CONbits.OCTSEL = 1; // Timer3 is selected for output compare1 OC2CONbits.OCTSEL = 1; // Timer3 is selected for output compare2 OC3CONbits.OCTSEL = 1; // Timer3 is selected for output compare3 OC4CONbits.OCTSEL = 1; // Timer3 is selected for output compare4 OC1CONbits.OCM = 6; OC2CONbits.OCM = 6;
// Output compare1 is in PWM mode with Fault pin disabled // Output compare2 is in PWM mode with Fault pin disabled
OC3CONbits.OCM = 6; OC4CONbits.OCM = 6;
// Output compare3 is in PWM mode with Fault pin disabled // Output compare4 is in PWM mode with Fault pin disabled
300
7
Output Devices
// Configure primary and secondary Output Compare1 registers for initial duty cycle OC1R = 60; // Initial PWM duty cycle value to compare against Timer3 OC1RS = 60; // Timer 3 duty cycle 1 = 1msec // Configure primary and secondary Output Compare2 registers for initial duty cycle OC2R = 120; // Initial PWM duty cycle value to compare against Timer3 OC2RS = 120; // Timer 3 duty cycle 2 = 2msec // Configure primary and secondary Output Compare3 registers for initial duty cycle OC3R = 180; // Initial PWM duty cycle value to compare against Timer3 OC3RS = 180; // Timer 3 duty cycle 3 = 3msec // Configure primary and secondary Output Compare4 registers for initial duty cycle OC4R = 240; // Initial PWM duty cycle value to compare against Timer3 OC4RS = 240; // Timer 3 duty cycle 4 = 4msec // Assign OC1 to RP4, OC2 to RP5, OC3 to RP6 and OC4 to RP7 TRISBbits.TRISB4 = 0; // RP4 is an output TRISBbits.TRISB5 = 0; // RP5 is an output TRISBbits.TRISB6 = 0; // RP6 is an output TRISBbits.TRISB7 = 0; // RP7 is an output RPOR2bits.RP4R = 18; // Redirect OC1 to RP4 RPOR2bits.RP5R = 19; // Redirect OC2 to RP5 RPOR3bits.RP6R = 20; // Redirect OC3 to RP6 RPOR3bits.RP7R = 21; // Redirect OC4 to RP7 // Enable Timer3 T3CONbits.TON = 1; // Timer3 starts operating with the internal clock = FP while (1); }
The system oscillator used in this application is an internal oscillator which requires five statements as shown in Fig. 7.34. The first three FOSC statements disable all clock-switching mechanisms, external oscillators and dedicated oscillator ports given by the #pragma config FCKSM ¼ 3, #pragma config OSCIOFNC ¼ 0, and #pragma config POSCMD ¼ 3 statements, respectively. The FOSCSEL register is the second register that configures the main oscillator in dspic33fj128mc802. In this register, the internal FRC oscillator with FRCDIVN output is selected to be the main oscillator by the #pragma config FNOSC ¼ 7 statement, and the internal-to-external oscillator switch-over mechanism is
7.6
Output Compare Unit
301
disabled by the #pragma config IESO ¼ 0 statement. The OSCTUN register sets the internal oscillator frequency to be 7.37MHz by the OSCTUNbits.TUN ¼ 0 statement. The FOSC and FCY frequencies become 115.15KHz and 28.8KHz by the CLKDIVbits.FRCDIV ¼ 6 and CLKDIVbits.DOZE ¼ 1 statements, respectively. // Quad PWM outputs generated by output compare unit with FRC oscillator #include // Configuration Register - FOSCSEL #pragma config IESO = 0 // start with a user-selected oscillator at reset #pragma config FNOSC = 7 // select FRC oscillator with postscalar at reset // Configuration Register - FOSC #pragma config FCKSM = 3 // both clock switching and fail-safe modes are disabled #pragma config OSCIOFNC = 0 // OSCO pin is a general purpose I/O pin #pragma config POSCMD = 3 // primary oscillators are disabled int main () { // OSCTUN Register OSCTUNbits.TUN=0; // CLKDIV Register CLKDIVbits.FRCDIV=6; CLKDIVbits.DOZE=1; CLKDIVbits.DOZEN=1;
// select FRC = 7.37MHz
// FRCDIVN = FRC/64 = 7.37/64 = 115.15KHz = FOSC // FOSC/4 = 115.15/4 = 28.8KHz = FCY // DOZE defines the ratio between cpu and peripheral clocks
… }
Fig. 7.34 Configuration of oscillator registers in Program 7.2
The OC registers are programmed to produce four independent PWM waveforms according to Fig. 7.35. In this program, Timer3 (TMR3) has been activated because Timer2 may be used by the PWM2 unit. The first three statements of the Timer3 control register, T3CON, configure the TMR3 clock frequency. The statement, T3CONbits.TGATE ¼ 0, disables the gated-clock feature of the timer clock. The Pre-scale factor for the TMR3 clock, TCKPS, is set to 1:1 by the statement, T3CONbits.TCKPS ¼ 0, so that Timer3 receives the peripheral clock at 57.6KHz. Finally, the Clock Source statement, T3CONbits.TCS ¼ 0, uses the internal peripheral clock, FP, for Timer3 instead of using an external clock. The next two entries in Fig. 7.35 set up TMR3. The TMR3 ¼ 0 statement initializes the timer contents to zero before counting starts. The PWM period is set by the period register, PR3, that resides in Timer3. Adjusting PR3 equal to PR3 ¼
302
7
Output Devices
// Quad PWM outputs generated by output compare unit with FRC oscillator #include … int main () { … // Program Timer3 control register T3CONbits.TGATE = 0; T3CONbits.TCKPS = 0; T3CONbits.TCS = 0;
// Gated (with external clock) timer clock is disabled // Prescale is 1:1 so the Timer3 clock = FP = 57.6KHz // Timer3 works with the internal clock -> FP = 57.6KHz
// Program Timer3 time base TMR3 = 0; // Timer3 counter register PR3 = 1200; // Timer3 period register -> PWM period // Configure Output Compare1 control register to work with Timer3, OC1CON OC1CONbits.OCTSEL = 1; // Timer3 is selected for output compare1 OC2CONbits.OCTSEL = 1; // Timer3 is selected for output compare2 OC3CONbits.OCTSEL = 1; // Timer3 is selected for output compare3 OC4CONbits.OCTSEL = 1; // Timer3 is selected for output compare4 OC1CONbits.OCM = 6; // Output compare1 is in PWM mode with Fault pin disabled OC2CONbits.OCM = 6; // Output compare2 is in PWM mode with Fault pin disabled OC3CONbits.OCM = 6; // Output compare3 is in PWM mode with Fault pin disabled OC4CONbits.OCM = 6; // Output compare4 is in PWM mode with Fault pin disabled // Configure primary and secondary Output Compare1 registers for initial duty cycle OC1R = 60; // Initial PWM duty cycle value to compare against Timer3 OC1RS = 60; // And it produces 1msec duty cycle // Configure primary and secondary Output Compare2 registers for initial duty cycle OC2R = 120; // Initial PWM duty cycle value to compare against Timer3 OC2RS = 120; // And it produces 2msec duty cycle // Configure primary and secondary Output Compare3 registers for initial duty cycle OC3R = 180; // Initial PWM duty cycle value to compare against Timer3 OC3RS = 180; // And it produces 3msec duty cycle // Configure primary and secondary Output Compare4 registers for initial duty cycle OC4R = 240; // Initial PWM duty cycle value to compare against Timer3 OC4RS = 240; // And it produces 4msec duty cycle
Fig. 7.35 Setting up the OC registers to generate four OC waveforms in Program 7.2
7.6
Output Compare Unit
303
// Assign OC1 to RP4, OC2 to RP5, OC3 to RP6 and OC4 to RP7 TRISBbits.TRISB4 = 0; // RP4 is an output TRISBbits.TRISB5 = 0; // RP5 is an output TRISBbits.TRISB6 = 0; // RP6 is an output TRISBbits.TRISB7 = 0; // RP7 is an output RPOR2bits.RP4R = 18; // Redirect OC1 to RP4 RPOR2bits.RP5R = 19; // Redirect OC2 to RP5 RPOR3bits.RP6R = 20; // Redirect OC3 to RP6 RPOR3bits.RP7R = 21; // Redirect OC3 to RP7 // Enable Timer3 T3CONbits.TON = 1;
// Timer3 starts operating with the internal clock = FCY
… }
Fig. 7.35 (continued)
1200 sets the maximum up-count value for this timer, at which point the OC hardware automatically resets TMR3, and restarts it from zero. All the OC control registers are programmed next. The OC1CONbits.OCTSEL ¼ 1 selects Timer3 (as opposed to Timer2) for the OC1 unit to define its duty cycle. The remaining three OC units are set up similarly by the statements, OC2CONbits. OCTSEL ¼ 1, OC3CONbits.OCTSEL ¼ 1 and OC4CONbits.OCTSEL ¼ 1. The statement, OC1CONbits.OCM ¼ 6, selects the mode for the OC1 unit, with disabled fault pin feature. The same holds true for the remaining OC units, using the OC2CONbits.OCM ¼ 6, OC3CONbits.OCM ¼ 6 and OC4CONbits.OCM ¼ 6 statements. The primary and secondary OC registers, OCxR and OCxRS, define the duty cycle of the PWM waveform. The first OC duty cycle is set to be 1 msec long by the OC1R ¼ 60 and OC1RS ¼ 60 assignments. The second, third and fourth OC duty cycles are set to be 2 msec, 3 msec and 4 msec by programming the corresponding OCxR and OCxRS registers with the values of 120, 180 and 240, respectively. This program uses the tri-state statement, TRISBbits.TRISB4 ¼ 0, and the PPS statement, RPOR2bits.RP4R ¼ 18, to make RB4 an output port for the OC1 unit. Similarly, the TRISBbits.TRISB5 ¼ 0 and RPOR2bits.RP5R ¼ 19 statements assign RB5 to be the output for OC2, the TRISBbits.TRISB6 ¼ 0 and RPOR3bits.RP6R ¼ 20 statements assign RB6 to be the output for OC3, and finally the TRISBbits. TRISB7 ¼ 0 and RPOR3bits.RP7R ¼ 21 statements assign RB7 to be the output for OC4. The last statement, T3CON1bits.TON ¼ 1, turns on TMR3 to deliver four independent PWM signals. After Timer3 is turned on, the while (1) statement establishes the continuous delivery of four PWM waveforms from all four OC ports, OC1, OC2, OC3 and OC4, as shown in Program 7.2.
304
7.7
7
Output Devices
Servo Operation
Servo is the simplest output device as shown in Fig. 7.36 [3]. This device has three terminals: power (usually 4.8V or 6V), ground and signal illustrated in Fig. 7.37.
Fig. 7.36 A typical servo with a circular servo arm
SIDE VIEW
TOP VIEW +90o
servo arm
servo arm 0o
-90o +Vcc signal ground
+Vcc signal ground
Fig. 7.37 Servo side and top views with its leads
The signal that controls the servo arm is a PWM waveform that has a pulse length between 1 msec and 2 msec (sometimes 2.5 msec depending on the manufacturer) in a 20 msec period as given in Fig. 7.38. When a 1 msec pulse is applied to the signal terminal, the servo arm rotates -90o to the left. Similarly, a PWM signal with a 2 msec pulse width rotates the arm +90o. The neutral arm position at 0o, and requires a 1.5 msec pulse width. However, most off-the-shelf servos found in hobby stores
7.8
Stepper Motors
305
can only rotate a maximum of +/- 45o due the an internal resistor restricting the servo arm movement. -90o 1msec 0o 1.5msec +90o 2msec TPWM (20msec for analog servo)
Fig. 7.38 Servo signal duty cycle is 1msec for -90 and 2msec for +90 degree rotation
7.8
Stepper Motors
Stepper motors are the next most common output device due to its precision in movement and torque. There are two types of stepper motors: unipolar and bipolar [4]. Unipolar stepper motors use a single power supply and two adjacent coils divided by a center connection used as a power connection. Bipolar stepper motors usually use two power supplies, deliver more torque, and have continuous windings without any center connection. Because of its common use and easier control we will examine the unipolar stepper motors in this section. A disassembled stepper motor is shown in Fig. 7.39. The rotor with green rectangular lines in the front view is composed of many permanent magnets adjacent
Fig. 7.39 Disassembled steeper motor with its rotor (front view) and stator (back view)
306
7
Output Devices
to each other. The stator in the back view has eight coils that change polarity in order to spin the rotor. A unipolar stepper motor structure is shown in Fig. 7.40. This figure shows only two permanent magnets on the rotor and four electromagnets on the stator so that its functionality can be explained in simpler terms.
Fig. 7.40 Basic unipolar stepper motor with four poles
Vcc
Vcc RL
RL
RL
A
RL
B
A
B
1
N 4
2 S
3
There are six control wires that come out of the motor. Two of these wires are internally tied together and connected to ground. The other four wires are control wires are named A, B, A and B. The rotor has a permanent magnet. The stator has four electromagnets which can be polarized either in the north or south directions. In this figure, when the bipolar transistor A is turned on, the current flows into the pole no. 3, and polarize this pole as a north pole (shown in red). The current continues its path and reaches the pole no. 1. It polarizes this pole as a south pole
7.8
Stepper Motors
307
(shown in black), and terminates at the ground connection. Thus, the permanent north and south poles on the rotor become attracted to the south and north poles on the stator, respectively, and the rotor aligns itself as shown in the figure. If a continuous clockwise rotation of the motor in 90o steps is desired, the waveform sequence in Fig. 7.41 should be applied to the bipolar transistors, A, B, Aand B. This sequence achieves the full rotation of the rotor in Fig. 7.42.
STEP NO
1
2
3
4
1
2
3
4
A B A B
Fig. 7.41 Control sequence to turn rotor in 90 degree increments
In step 1, only the transistor A is turned on, and the stator poles no. 3 and no. 1 become polarized in the south and north directions, respectively, as discussed in Fig. 7.40. In step 2, the transistor B is turned on, and the resulting current polarizes the stator poles no. 4 and no. 2. The rotor departs from its previous position and rotates 90o in clockwise direction. In step 3, when the transistor A is turned on, the stator poles no. 1 and no. 3 become polarized again, but in opposite directions compared to the polarization directions in step 1: the coil no. 1 becomes north pole, and the coil no. 3 becomes south pole. As a result, the rotor makes another 90o clockwise turn. Finally in step 4, the transistor B is turned on, and this time the current polarizes the coil no. 2 and no. 4 as the north and south poles, respectively. This turns the rotor for another 90o clockwise direction, completing a full rotation.
308
Output Devices
7 Vcc
RL
RL
RL
A
Vcc
Vcc
STEP 1
B
A
RL
RL B
Vcc
STEP 2 RL
RL
A
RL
B
A COM
B
COM
1
1
N 4
2
N
2
S
4 S
3
Vcc RL
3
RL
RL
A
RL
RL
B
A
Vcc
Vcc
STEP 3
B
Vcc
STEP 4 RL
RL
A
RL
B
A COM
B
COM
1
1
4
S
2
N
S 4
2
N 3
3
Fig. 7.42 90o rotations in unipolar stepper motor
However, a full rotation can also be achieved by polarizing two coils simultaneously with the same polarity as shown in Fig. 7.43. Since there are now two coils acting as north poles, and two coils as south poles, the torque will be doubled. However, this scheme also doubles the power consumption because all four coils become active in each step.
7.8
Stepper Motors Vcc RL
309
RL
A
Vcc
Vcc
STEP 1 RL B
A COM
RL
RL B
RL
A
RL
COM
1
S
N
4
2
4
2 N
S
3
3
Vcc
STEP 3 RL
RL
A
B
COM
1
RL
RL
B
A
COM
Vcc
Vcc
STEP 2
RL
B
A COM
Vcc RL B
RL
RL
A
COM
1
RL
B
A
COM
B
COM
N
S
1
2
4
2 S
N
4
Vcc
STEP 4
3
3
Fig. 7.43 90o rotations in a unipolar stepper motor, resulting more torque
In step 1, the transistors A and B are turned on, the coils no. 4 and no. 3 become north poles, the coils no. 1 and no. 2 become south poles. As a result, the rotor turns 45o in clockwise direction. In step 2, the transistors A and B are turned on, resulting the coils no. 1 and no. 4 polarized as north poles, the coils no. 2 and no. 3 as south poles. The rotor makes another 90o rotation from its previous position. In steps 3 and 4, the rotor moves in 90o increments, thus completing a 360o rotation. In order to achieve the full 360o rotation in Fig. 7.43, the following waveform sequence in Fig. 7.44 should be applied to the A, B, A and B inputs.
310
7
STEP NO
1
2
3
4
1
2
3
Output Devices
4
A B A B
Fig. 7.44 Control sequence to turn rotor in 90o increments, resulting more torque
Note that the waveforms in this figure are still PWM-like, but with logic 1 levels overlapping each other. The overlapping periods can be generated by activating the dead time feature in the motor control PWM1 unit. The time interval in each step and the amount of overlap define the speed of the rotor. The same stepper motor structure in Fig. 7.40 can be used to engage 45o rotor movements instead of 90o. However, this method requires activating two coils in one step and four coils in the next step as shown in Fig. 7.45a, b. If the rotor rotation is not continuous but limited to a finite number of steps, this action will result in a weaker torque when only two coils are active, but produce stronger torque when all four coils become active.
7.8
Stepper Motors Vcc RL
311 Vcc
STEP 1 RL
A
RL
Vcc RL
B
A
RL
Vcc
STEP 2 RL
B A
RL
RL
B
A COM
B
COM
1
1
N N
4
2
4
2 S
S
3
Vcc RL
3
Vcc
STEP 3 RL
A
RL
RL
RL
Vcc
STEP 4 RL
RL
B A
B
A COM
Vcc
B
A COM
COM
RL B
COM
1
1
S
N
S
4
2
4
2 N
3
3
Fig. 7.45 (a) 45o rotations in a unipolar stepper motor, achieving half a revolution (steps 1 to 4) (b) 45o rotations in a unipolar stepper motor, completing the other half (steps 5 to 8)
312
7 Vcc RL
Vcc
STEP 5 RL
A
RL
Vcc RL
Vcc
STEP 6 RL
RL
B A
B
A
RL
Output Devices
RL
B
A COM
B
COM
1
1
S
S 2
4
2 N
4 N
3
3
Vcc RL
Vcc
STEP 7 RL
A
RL
RL
B
A COM
Vcc RL
Vcc
STEP 8 RL
B A
RL B
A
COM
RL
COM
B
COM
1
2
4
2 S
S
4
N
N
1
3
3
Fig. 7.45 (continued)
To achieve a full rotation in Fig. 7.45a, b, the waveform sequence in Fig. 7.46 is required. These waveforms also overlap with each other, and require activating dead time registers in the motor control PWM units.
7.9
Brushless DC Motors
STEP NO
1
2
3
313
4
5
6
7
8
1
2
3
A B A B
Fig. 7.46 Control sequence to turn rotor in 45 degree increments
7.9
Brushless DC Motors
Brushless motors have been increasingly popular in automotive industry, particularly in electric vehicles, military, heavy metal and machining industries [5]. This type of motor does not have any mechanical commutator used in traditional brushed motors, which improves their reliability and durability. Brushed motors, on the other hand, are exposed to constant friction between the stationary brushes and spinning rotor which eventually causes wear and power loss as shown in Fig. 7.47. Another advantage of this type of motor is the size. Being smaller and lighter than a brushed motor with the same power output, a brushless motor is more suitable for applications where space is limited. Brushless motors come in one-phase, two-phase and three-phase types. The latter type is the most common and will be examined in this section. The stator of the brushless motor consists of many steel laminated electromagnets distributed evenly around the inner circumference of the motor shell as shown in Fig. 7.48. The rotor is
Fig. 7.47 Brushed DC motor with four stator brushes (left) and its copper wound rotor (front)
314
7
Output Devices
Fig. 7.48 Brushless DC motor with its rotor (left) and stator (right)
made out of multiple pairs of permanent magnets placed next to each other. Therefore, brushless motors structurally resemble to stepper motors. Even though the structure of a brushless motor is simple, its electronic control to active its poles in a correct sequence with precise timing may be challenging. Figure 7.49 shows a simplified block diagram how to control a brushless motor. In this figure, the transistors T0 to T5 can be N-channel power MOSFETs instead of +Vcc
PWM1
T3
T1
IA
T5
A PWM3 PWM5 B
C PWM4 PWM2
PWM0
IC T2
T0
T4
IB -Vcc
T3 – T4 T1 – T4
T3 – T0
T5 – T0
T1 – T2 T5 – T2
Fig. 7.49 Simplified block diagram of a three-phase brushless motor control
7.9
Brushless DC Motors
315
NPN bipolar transistors. Each diode across the collector-emitter junction of the bipolar transistor prevents reverse induced currents from accidentally entering the emitter junction. In such an event, the reverse current simply flows through the diodes, protecting the NPN bipolar transistor. The inductors, A, B and C, represent the three phases of the motor (stator). Each winding receives the currents, IA, IB and IC, either in the positive direction as represented in the figure or in reverse. The circle below the control circuit shows the firing (activation) sequence of the transistors from T0 to T5. According to this sequence, if T5 and T0 are turned on first, T3 and T0 become the next pair to be active. This is followed by the third pair, T3 and T4, then the fourth pair, T1 and T4, then the fifth pair, T1 and T2, and finally the sixth pair, T5 and T2, before the first pair, T5 and T0, are turned on once again. The firing sequence of the transistors and the currents through brushless motor windings are shown in Fig. 7.50. According to this figure, if the first active pair of transistors is T5 and T0, then the current starts flowing from the positive power source, +VCC, through the collector-emitter junction of T5 to the winding C. This current becomes +IC when it runs through the winding C, and –IA when it enters the winding A. Finally, it flows through the collector-emitter junction of T0, and terminates at the negative power source, –VCC. This corresponds to the first column in the timing diagram in Fig. 7.50. Similarly, when T3 and T0 are turned on, the current flows through the collector-emitter junction of T3, and becomes +IB when it enters the winding B. Then the current becomes –IA when it flows through the winding A and the collector-emitter junction of T3 before it reaches –VCC. This sequence corresponds to the second column in the timing diagram in Fig. 7.50. The remaining instances turn on the transistors, T3 and T4, which produce +IB and –IC (column 3), the transistors, T1 and T4, which produce +IA and –IC (column 4), the transistors, T1 and T2, which produce +IA and –IB (column 5), and finally the transistors, T5 and T2, which produce +IC and –IB (column 6), before the transistors, T5 and T0, are turned on again in column 7.
316 Fig. 7.50 Firing sequence of transistors, T0 to T5, in Fig. 7.49
7 1
2
3
4
5
6
7
8
Output Devices 9
10
11
T5 T0 T3 T4 T1 T2
IA
IB
IC
As in the stepper motor, the DC brushless motor also requires PWM signals that overlap with each other. However, the motor control PWM unit in dspic33fj128mc802 can generate these waveforms with its six high-polarity and six low-polarity PWM outputs. Projects 1. Generate a single PWM signal from the PWM1H1 output of dspic33fj128mc802 using Program 7.1. 2. Generate eight PWM signals from the PWM1H1, PWM1L1, PWM1H2, PWM1L2, PWM1H3, PWM1L3, PWM2H, PWM2L outputs of dspic33fj128mc802 using Fig. 7.25. Use a logic analyzer to obtain the waveforms in Fig. 7.26. 3. Generate four PWM outputs from the output compare unit according to Program 7.2. Use a logic analyzer to obtain the waveforms in Fig. 7.30. 4. Use the program in question 1 to continuously rotate the servo between -90o and +90o (or -45 and +45o in hobby-style off-the-shelf servos) as shown in Fig. 7.38.
References
317
5. Use PWM1, PWM2 or output compare units to generate the control signals to rotate a unipolar stepper motor in 90o increments as shown in Fig. 7.41. 6. Use a program similar to the one in question 5 to generate control signals for a unipolar stepper motor in Fig. 7.44. 7. Similar to questions 5 and 6, write a program to generate control signals to rotate a unipolar stepper motor in 45o increments in Fig. 7.46. 8. Generate the firing sequence in Fig. 7.50 to operate a brushless motor. 9. Use ADXL362 3-axis accelerometer on a platform that tilts between -90o and +90o, and acts as an input to dspic33fj128mc802 microcontroller to rotate a single hobby-grade servo arm between -45o and +45o. The servo can be connected to any PWM1 output.
References 1. dspic33fj128mc802/804 Microchip processor datasheet, https://www.microchip.com/en-us/ product/dsPIC33FJ128MC802, Accessed January 2023. 2. MPLABX software design environment, https://www.microchip.com/en-us/tools-resources/ develop/mplab-x-ide, Accessed January 2023. 3. Hitech servos, https://hitecrcd.com/products/servos, Accessed January 2023. 4. Generic unipolar and bipolar stepper motors, https://www.powerelectronictips.com/unipolar-vsbipolar-drive-for-stepper-motors-part-1-principles-faq/, Accessed January 2023. 5. Generic brushless DC motors, https://en.wikipedia.org/wiki/Brushless_DC_electric_motor, Accessed January 2023.
Appendix Designing Front-End Electronics
Operational Amplifier Properties This section provides the reader some basic understanding of operational amplifier circuits, and their use in designing the front-end electronics for sensors. Many types of sensors produce analog signals in micro-volts or micro-amps range which need to be amplified to volts-scale prior to analog-to-digital conversion. Some of these sensors are ground-able, and some are not. They may also produce unwanted signals, such as noise, embedded in their output. Therefore, as sensor signals need to be amplified they also have to be “cleaned” from all unwanted signals and interferences before they can be used by an application program that resides in the microcontroller’s data memory. In general terms, operational amplifiers are dual input, single output devices that exhibit high input impedance, low output impedance and large gain with a limited frequency bandwidth. Important characteristics of an operational amplifier are shown in Fig. A.1. The input currents, I1 and I2, of this amplifier are very close to GAIN = ~100,000 IN1 VIN1-VIN2 ≈ 0V IN2
I1 ≈ 0A OUT I2 ≈ 0A ROUT → 0 Ω
RIN → ∞ Ω Fig. A.1 Characteristics of an operational amplifier © The Editor(s) (if applicable) and The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 A. Bindal, Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers, https://doi.org/10.1007/978-3-031-27841-9
319
320
Appendix Designing Front-End Electronics
0A because of the high impedance at the negative and positive input terminals. A milli-volt range voltage difference between the input terminals, (VIN1-VIN2), can saturate the output of the operational amplifier to either the positive or negative power supply voltage, depending on the polarity of (VIN1-VIN2). However, for the purposes of simple circuit analysis, we can approximate this difference to be 0V when analyzing an operational amplifier circuit.
Voltage Amplifier Circuits for Sensors Inverting Voltage Sensor Amplifier The following operational amplifier circuit in Fig. A.2 produces voltage amplification, which results an inverted output, if one of the sensor terminals needs to be grounded. Fig. A.3 shows the current and voltage assignments of the circuit in Fig. A.2 for analysis.
Substituting I =
VSEN R1
VSEN = R1 I
ð1Þ
VSEN = ðR1 þ R2 Þ I þ VOUT
ð2Þ
into Eq. 2 yields:
R2
R1 VOUT VSEN
Fig. A.2 Inverting voltage sensor amplification circuit if one sensor terminal needs to be grounded
Appendix Designing Front-End Electronics
321
R2 I R1 I
0V
VSEN
VOUT
0A
Fig. A.3 Current and voltage assignments of the inverting sensor amplifier in Fig. A.2
VSEN = ðR1 þ R2 Þ VSEN = VSEN þ
VSEN þ VOUT R1
R2 V þ VOUT R1 SEN
VOUT R =- 2 VSEN R1
ð3Þ
Non-Inverting Voltage Sensor Amplifier The operational amplifier circuit in Fig. A.4 produces voltage amplification with a non-inverted output if one sensor terminal needs to be grounded. Fig. A.5 shows the current and voltage assignments of the circuit in Fig. A.4 for analysis.
Substituting I = -
VSEN R1
0 = R1 I þ VSEN
ð4Þ
0 = ðR1 þ R2Þ I þ VOUT
ð5Þ
into Eq. 5 yields:
322
Appendix Designing Front-End Electronics
R2
R1 VOUT
VSEN
Fig. A.4 Non-inverting voltage sensor amplification circuit if one of the sensor terminals needs to be grounded
R2 I R1 I
0A
VOUT
0V 0A VSEN
Fig. A.5 Current and voltage assignments of the inverting sensor amplifier in Fig. A.4
0 = - ðR1 þ R2 Þ
ðR1 þ R2 Þ
VSEN þ VOUT R1
VSEN = VOUT R1
Appendix Designing Front-End Electronics
323
VOUT ðR1 þ R2 Þ = R1 VSEN
ð 6Þ
Isolation Circuit with Unity Gain Figure A.6 represents the isolation circuit if the sensor requires very large input impedance before being interfaced to another circuitry. This circuit practically eliminates any output loading (isolation) on the sensor. One terminal of the sensor has to be grounded in order to use the isolation circuit. Fig. A.6 Isolation circuit with unity gain
0A 0A 0V
VOUT 0A ROUT ® 0Ω
VSEN RIN ®
Ω
The only equation available for this circuit is: VSEN = 0 þ VOUT
ð7Þ
VOUT =1 VSEN
ð8Þ
Voltage Sensor Amplifier with Signal and Reference Inputs If the sensor output needs to be compared against a reference voltage, a differential amplifier circuit in Fig. A.7 is used. This circuit produces a differential amplification of the input signals, (VSEN – VREF), provided that one of the sensor (and the reference signal) terminals is connected to ground. Figure A.8 shows the current and voltage assignments of the circuit in Fig. A.7 for analysis.
324
Appendix Designing Front-End Electronics
R2
R1
VOUT
R3
VSEN
R4
VREF
Fig. A.7 Voltage sensor amplification with a reference voltage if one sensor terminal is grounded
R2
I1
R1
I1
0A I2
VSEN
0A R3
VREF
VOUT
0V
I2
R4
Fig. A.8 Current and voltage assignments of the sensor amplifier in Fig. A.7
VSEN = ðR1 þ R2 Þ I1 þ VOUT
ð9Þ
VREF = ðR3 þ R4 Þ I2
ð10Þ
R4 I2 = R2 I1 þ VOUT
ð11Þ
From Eq. 11, I2 =
R2 V I þ OUT R4 1 R4
Substituting Eq. 12 into Eq. 10 yields:
ð12Þ
Appendix Designing Front-End Electronics
VREF = ðR3 þ R4 Þ
325
R2 V I þ ðR3 þ R4 Þ OUT R4 1 R4
ð13Þ
or I1 =
R4 VREF - ðR3 þ R4 ÞVOUT R2 ðR3 þ R4 Þ
ð14Þ
Finally, substituting Eq. 14 into Eq. 9 yields: VSEN = ðR1 þ R2 Þ
R4 VREF - ðR3 þ R4 ÞVOUT þ VOUT R2 ðR3 þ R4 Þ
ð15Þ
Reorganizing the terms in this equation produces: VSEN =
R4 ðR1 þ R2 Þ R V - 1V R2 ðR3 þ R4 Þ REF R2 OUT
Thus, VOUT = -
R2 R ðR1 þ R2 Þ V þ 4 V R1 SEN R1 ðR3 þ R4 Þ REF
ð16Þ
If R1 = R3 and R2 = R4, then Eq. 16 simplifies as: VOUT = -
R2 R ðR1 þ R2 Þ V þ 2 V R1 SEN R1 ðR1 þ R2 Þ REF
VOUT = -
R2 ðV - VREF Þ R1 SEN
ð17Þ
In Eq. 17, the amplification is adjusted by the ratio of RR21 :
Summation Voltage Sensor Amplifier It is possible to add two sensor inputs according to Fig. A.9 provided that both sensors have one of their terminals grounded. Fig. A.10 shows the current and voltage assignments of the circuit in Fig. A.9 for analysis.
326
Appendix Designing Front-End Electronics
R1
VSEN1 R2
R1 VOUT
VSEN2
Fig. A.9 Summation voltage sensor amplifier if one of the sensor terminals is grounded
R1 I1 VSEN1 R2 (I1 + I2) R1 0A
I2 VSEN2
0V
VOUT
0A
Fig. A.10 Current and voltage assignments of the summation sensor amplifier in Fig. A.9
VSEN1 = R1 I1 þ R2 ðI1 þ I2 Þ þ VOUT
ð18Þ
VSEN2 = R1 I2 þ R2 ðI1 þ I2 Þ þ VOUT
ð19Þ
Appendix Designing Front-End Electronics
327
R2 ð I1 þ I2 Þ þ VOUT = 0
ð20Þ
Eliminating I1 and I2 in Eqs. 18, 19 and 20 results in: VOUT = -
R2 ðV þ VSEN2 Þ R1 SEN1
ð21Þ
The output voltage in Eq. 21 forms the inverted sum of the sensor voltages, VSEN1 and VSEN2, with a amplification factor of RR21 :
Voltage Sensor Amplifier with Ungrounded Sensor Terminal If neither of the sensor terminals can be grounded, then the circuit in Fig. A.11 is used to obtain an amplified but an inverted sensor signal at the output. This schematic is similar to the differential amplifier schematic in Fig. A.7; however, the sensor in this circuit is not grounded. The resistors, R3 and R4, are also replaced with the resistors, R1 and R2, respectively.
R2
R1 VSEN
VOUT
R1
R2
Fig. A.11 Voltage amplification of a sensor if no sensor terminal is grounded
Figure A.12 shows the current and voltage assignments of the circuit in Fig. A.11 for analysis. In this circuit, the voltage drop at the input of the operational amplifier is assumed 0V. Similarly, the currents going to the positive and negative input terminals of the operational amplifier are assumed to be very close to 0A.
328
Appendix Designing Front-End Electronics
R2
R1
I
0A VSEN
VOUT
0V 0A I
R1 R2
Fig. A.12 Current and voltage assignments of the sensor amplifier in Fig. A.11
Therefore, from Fig. A.12 we have: IðR1 þ R2 Þ–VSEN þ IðR1 þ R2 Þ þ VOUT = 0
ð22Þ
or VSEN –VOUT = 2IðR1 þ R2 Þ We also have: VSEN = IðR1 þ R1 Þ = 2I R1
I=
VSEN 2R1
ð23Þ
Substituting Eq. 23 into Eq. 22 yields: VSEN - VOUT = 2ðR1 þ R2 Þ
VSEN 2R1
or VOUT = -
R2 V R1 SEN
ð24Þ
Eq. 24 shows the sensor voltage at the input of Fig. A.11 is inverted and amplified with a ratio of RR21 :
Appendix Designing Front-End Electronics
329
Trans-Resistance Amplifier Circuits for Sensors Trans-resistance amplifiers are for sensors that generate current instead of voltage. These sensors are usually opto-electronic devices such photodiodes, photo-detectors and solar cells. These specialty amplifiers are designed to convert microampererange currents into voltages.
Inverting Trans-Resistance Sensor Amplifier The operational amplifier circuit in Fig. A.13 produces inverted sensor amplification if one sensor terminal is grounded. Figure A.14 shows the current and voltage assignments of the circuit in Fig. A.13 for analysis. Fig. A.13 Inverting transresistance sensor amplifier if one sensor terminal is grounded
R
VOUT ISEN
Fig. A.14 Current and voltage assignments of the trans-resistance amplifier in Fig. A.13
R
ISEN
0A
ISEN
VOUT
0V 0A
330
Appendix Designing Front-End Electronics
The Kirchoff’s voltage law for this circuit becomes: 0 = R ISEN þ VOUT
ð25Þ
VOUT = - R ISEN
ð26Þ
Therefore,
Here, the input sensor current, isen, is inverted and amplified by the magnitude of R at the output.
Non-Inverting Trans-Resistance Sensor Amplifier The following operational amplifier circuit in Fig. A.15 produces non-inverted sensor amplification if one sensor terminal is grounded. This circuit basically combines the inverting trans-resistance amplifier in Fig. A.13 and the inverting voltage amplifier in Fig. A.2 with unity gain. R R
R
ISEN
VINT
VOUT
Fig. A.15 Non-inverting trans-resistance sensor amplifier if one sensor terminal is grounded
Repeating Eq. 26 for the first stage yields: VINT = - R ISEN For the second stage, we have : VOUT = -
R V = - VINT R INT
ð27Þ
Appendix Designing Front-End Electronics
331
Substituting VINT in Eq. 27 yields: VOUT = - ð- R ISEN Þ = R ISEN
ð28Þ
Analog Voltage Comparator Voltage comparators are used to compare two analog inputs to produce an output signal equal either to the positive supply voltage value of the operational amplifier or to 0V (the negative supply voltage can be 0V or a negative value depending on the operational amplifier datasheet). Figure A.16 shows the circuit diagram of a comparator. Here, voltage levels at the N and P nodes are compared to each other. If the voltage at P becomes larger than N, VOUT transitions to VCC and the output LED turns on. Otherwise, VOUT stays at 0V and turns off the LED. Fig. A.16 Basic comparator circuit
+VCC +VCC N +VCC
VOUT P RZ
A good application of a voltage comparator is a voltage monitor circuit. Suppose changes in the 2.5 V power supply voltage of a CPU have to be monitored constantly. A Reset signal needs to be generated if the supply voltage drops below 2 V. A circuit shown in Fig. A.17 can be used as a voltage monitor. In this circuit, a Zener diode with a value of 1.25 V is connected in series with RZ, which provides the biasing current for the diode. The voltage at the negative terminal of the operational amplifier must also be set at 1.25 V by R1 = R2. Thus, VS =
R2 V V = CC 2 ðR1 þ R2 Þ CC
ð29Þ
332
Appendix Designing Front-End Electronics
Now, if VCC drops below 2.5 V, such as 2 V, VS proportionally changes: VS =
VCC 2 = = 1V 2 2
Since this voltage is less than VZ = 1.25 V, Reset output in Fig. A.17 switches to 2.5 V and resets the CPU. Fig. A.17 A voltage monitor circuit
2.5V R1 VS R2
2.5V
Reset
2.5V RZ VZ
Schmitt Trigger Most TTL and CMOS logic gates require definite and fast high/low logic transitions at their inputs. If the input signal transitions above or below predefined logic levels, it may cause accidental logic transitions for the gate. Therefore, the elements that cause false logic transitions must be identified and filtered by a mechanism before they can reach the input of a logic circuit. Similarly, if the input signal is too slow to rise or fall, excessive current drainage can also occur in the logic gate. Therefore, Schmitt triggers are “buffer” devices that are designed to remove slow or noisy logic transitions from input signals (such as from an electro-mechanical relay), and transform them into forms that will meet the rise/fall time and logic level specifications of a TTL or CMOS logic family. The basic Schmitt trigger circuit is shown in Fig. A.18. In this figure, the output transitions from one supply voltage (+VCC or –VCC) to the next as soon as the input voltage exceeds a predefined voltage limit. Schmitt trigger operates under the same
Appendix Designing Front-End Electronics
333
principle as the voltage comparator. However, it compares the input voltage against two set of values instead of one, and its feedback loop ensures that a portion of its output is always taken into account when the comparison is performed at its input. Fig. A.18 Basic Schmitt trigger
+VCC VIN
VREF
VOUT
I2
0A
R2 -VCC VF I1
R3
I3
R1
We can adjust and set the voltage values in a Schmitt trigger by adjusting the values of R1, R2 and R3. If I1, I2 and I3 are the currents passing through R1, R2 and R3 in Fig. A.18, then we can write the following: I1 = I2 þ I3
ð30Þ
However, VF R1
ð31Þ
I2 =
ðVREF - VF Þ R2
ð32Þ
I3 =
ðVOUT - VF Þ R3
ð33Þ
I1 =
Substituting Eqs. 31, 32 and 33 into Eq. 30 yields: VF ðVREF - VF Þ ðVOUT - VF Þ = þ R1 R2 R3
334
Appendix Designing Front-End Electronics
VF If we define R1123 =
1 R1
1 1 V V 1 þ þ = REF þ OUT R2 R3 R1 R2 R3
þ R12 þ R13 , then VF V V = REF þ OUT R123 R2 R3
VF = If VOUT = +VCC
R123 R V þ 123 VOUT R2 REF R3
then VF becomes: VFPOS =
Similarly, If VOUT = -VCC
ð34Þ
R123 R V þ 123 VCC R2 REF R3
ð35Þ
then VF becomes: VFNEG =
R123 R V - 123 VCC R2 REF R3
ð36Þ
Here, VFPOS and VFNEG are the two feedback voltages at the output terminal when VOUT = +VCC and VOUT = –VCC, respectively. In this example, let us set the voltage limit for the low-to-high logic transition at 3.3 V, and the voltage level for the high-to-low logic transition to 0 V for an input voltage that swings between +VCC = +5 V and -VCC. = 5 V. Therefore, R1 = R2 = R3 = 3KΩ VREF = 5V, þ VCC = 5V and–VCC = –5V Then: R123 = 1KΩ VFPOS =
R123 R123 1 1 10 5þ 5= VREF þ VCC = = 3:3V R2 R3 3 3 3
VFNEG =
R123 R123 1 1 55 = 0V VREF VCC = R2 R3 3 3
ð37Þ
ð38Þ
The operation and the output waveform generation of the Schmitt trigger are explained in Fig. A.19 according to these numerical values. If VIN is less than the
Appendix Designing Front-End Electronics
335
first set value, VFPOS = 3.3 V, then VOUT transitions to +VCC = 5 V. For input voltages greater than 3.3 V, VOUT transitions to –VCC = -5 V, and VF becomes VFNEG = 0 V. Here, VOUT stays at -5 V until VIN drops below the second set value, 0 V. As soon as VIN is less than 0 V, VOUT transitions back to +VCC = 5 V, and VF becomes VFPOS = 3.3 V. The next output change to -5 V does not take place until VIN reaches above 3.3 V.
VIN(t)
VFPOS = 3.3V
t
VFNEG = 0V
VOUT(t)
5
For VF = VFPOS = 3.3V
0
-5
t
For VF = VFNEG = 0V
Fig. A.19 Schmitt trigger input and output waveforms
Design Projects Designing the Electronics for an Analog Microphone The first design project simply prepares the analog signal from a microphone for the ADC. The microphone used in Fig. A.20 is analog microphone ADMP504 produced by Analog Devices. From the manufacturer’s datasheet, the microphone produces a maximum voltage of 0.25 V, which needs to be amplified to a maximum value of 5 V for the analog-to-digital converter, AD7819, also produced by Analog Devices. The
336
Appendix Designing Front-End Electronics
microphone produces an output DC offset voltage of 0.8 V that needs to be cleaned from the analog signal before the signal is amplified. Therefore, VREF in Fig. A.20 should be adjusted to a certain value that offsets the 0.8 V DC offset in the microphone output signal. R2
VREF R1
VOUT
AD7819
I ADMP504
VIN = VMIKE+VOFF
8
MICOUT
ADC
Microphone Fig. A.20 Microphone front end circuit
According to the datasheet we have: VIN = VMIKE þ VOFF
ð39Þ
Where, VMIKE is the analog voltage produced by the microphone without DC offset, and VOFF = 0.8 V. We also have: VREF = ðR1 þ R2 Þ I þ VOUT
ð40Þ
VREF = R1 I þ VIN
ð41Þ
From Eq. 41, I=
VREF - VIN R1
ð42Þ
Substituting Eq. 42 into Eq. 40 yields: VREF = ðR1 þ R2 Þ
VREF - VIN þ VOUT R1
ð43Þ
Rearranging the terms in Eq. 43 yields: VOUT =
ðR1 þ R2 Þ R VIN - 2 VREF R1 R1
Substituting Eq. 39 into Eq. 44 yields:
ð44Þ
Appendix Designing Front-End Electronics
VOUT = þ
337
ðR1 þ R2 Þ ðR þ R2 Þ R ðVMIKE þ VOFF Þ - 2 VREF = 1 VMIKE R1 R1 R1
ðR1 þ R2 Þ R VOFF - 2 VREF R1 R1
In order to isolate the term with VMIKE, we need to eliminate the terms with VOFF and VREF. Thus, ðR1 þ R2 Þ R VOFF = 2 VREF R1 R1 or VREF =
ðR1 þ R2 Þ VOFF R2
ð45Þ
VOUT =
ðR1 þ R2 Þ VMIKE R1
ð46Þ
Then,
From the datasheets, the maximum value produced by the microphone is VMIKE = 0.25 V and the maximum value at the input of the ADC is VOUT = 5 V. Therefore, from Eq. 46: ðR þ R 2 Þ 5 VOUT = = 20 = 1 R1 VMIKE 0:25 R2 = 19 R1
ð47Þ
We use R1 = 1 KΩ and R2 = 19 KΩ Using Eq. 15 in Eq. 13 yields: VREF = 1:05 × 0:8 = 0:85V
ð48Þ
Therefore, we must find a Zener diode at 0.85 V or a potentiometer that can produce 0.85 V. However, the latter choice is prone to temperature fluctuations.
338
Appendix Designing Front-End Electronics
Designing Electronics for a Temperature Measurement System The second design project amplifies the thermocouple signal and prepares it for the ADC. The thermocouple signal changes linearly with temperature as shown in Fig. A.21. From this figure, the thermocouple signal can be approximated as VT ≈ 0.0407 T in mV. However, at low temperatures the deviation from linearity introduces error as large as 2 mV. 12 10
Type K Thermocouple: Chromel-Alumel
8 6
VT (mV)
4 2 0 -2 -4 -6 Error » 2mV -8 -250
-200
-150
-100
-50
0 50 Temperature (0C)
100
150
200
250
Fig. A.21 Voltage-temperature characteristics of type-K thermocouple
In the first attempt, let us build a circuit that measures ambient temperatures between 0°C and 200°C. This is a very straightforward design that requires only signal amplification as shown in Fig. A.22. R2 R4 VT
Thermocouple Type-K
R1 R3 VP
VTEMP
AD7819 8
TEMPOUT
ADC
Fig. A.22 A circuit that measures temperatures between 0°C and 200°C
The amplification is performed by two cascaded inverting stages. In the first stage, we have:
Appendix Designing Front-End Electronics
339
VP R =- 2 VT R1
ð49Þ
R VTEMP =- 4 VP R3
ð50Þ
In the second stage,
We know the maximum voltages at VT = 8.14 mV (at T = 200°C) and VTEMP = 5 V. This requires an overall gain of approximately 614. If the first stage amplifies the thermocouple signal by 20 times, then the second stage will need an amplification of 30.7. Thus, R2 = 20 R1
ð51Þ
R4 = 30:7 R3
ð52Þ
Similarly,
Selecting R1 = 1 KΩ, R2 = 20 KΩ, R3 = 2 KΩ, and R4 ≈ 62 KΩ according to Eq. 51 and Eq. 52 will proportionally change the sensor readings at VTEMP between 0 V and 5 V.
VTEMP(mV)
VP(mV) +600 R4 R3
-200
1200
T (oC)
+200 -600 VCC
-200
R2
+200
T (oC)
R4
RF
R5
R1 R3 R5 VT
VP VSUM
R3 Thermocouple K-type
Pre-amplification
Summation
Inversion
-200
-8.14
8
VSUM(mV) +200
8.14 +200
T (oC)
T (oC)
R4 R3
TEMPOUT
AD7819
ADC
VT(mV)
-200
VTEMP
VOFF
1200
Fig. A.23 A circuit that measures temperatures between -200°C and 200°C
340
Appendix Designing Front-End Electronics
The second circuit in Fig. A.23 conditions the temperature readings at VT between -200°C and 200°C. Since the thermocouple voltage readings become negative for temperatures less than 0°C, the best method is to add a DC offset to the actual thermocouple signal to eliminate the negative values of VT. The maximum value of the “extrapolated” VT at -200°C is -8.14 mV according to Fig. A.21. This is a very small value to offset. Therefore, a better option is to amplify the thermocouple signal without any DC offset in the first stage, and then employ an offset voltage for the amplified signal in the next stage. The amplification in the first stage produces the result in Eq. 20. R þ R2 VP = 1 VT R1
ð53Þ
In the second stage, a steady DC offset voltage can be generated by a rectifying diode. Assuming the diode has VF = 600 mV, the amplification at the first stage must yield: R þ R2 600mV VP = 1 = ≈ 73:7 8:14mV VT R1
ð54Þ
In this equation, if R1 = 1 KΩ then R2 ≈ 72 KΩ. This amplification factor makes the value of VP to be approximately -600 mV at -200°C, and +600 mV at +200°C. For the second stage, when VOFF = 600 mV, then: VSUM = -
R4 R ðV þ VOFF Þ = - 4 ðVP þ 600mVÞ R3 P R3
ð55Þ
According to Eq. 22, when temperature drops to -200°C and VP becomes 600 mV, then: VSUM = -
R4 ð- 600mV þ 600mVÞ = 0V R3
ð56Þ
Similarly, when temperature rises to 200°C and VP becomes +600 mV, then: VSUM = -
R4 R ð600mV þ 600mVÞ = - 4 1200mV R3 R3
ð57Þ
A Zener diode can also be used to create a DC offset in the second stage. The smallest reference voltage from a Zener diode is approximately 1.8 V, and using this value in Eq. 54 requires a gain over 200 in the first stage. However, producing a high voltage gain from a single stage amplifier should be avoided because small deviations in resistor values introduce large errors in amplifier gain.
Appendix Designing Front-End Electronics
341
To calculate the biasing resistor value, RF, in the second stage requires determining the forward current, IF, of the rectifying diode. Assuming that VCC = 5 V, and this particular diode calls for IF = 1 mA in order to produce VF = 600 mV across its terminals, RF becomes approximately equal to 4.4 KΩ to set up the correct DC offset, VOFF. The third stage simply provides a voltage inversion. Thus: VTEMP = -
R5 R V = - VSUM = 4 ðVP þ VOFF Þ R5 SUM R3
ð58Þ
The maximum value of VTEMP should be around 5 V before the analog-to-digital converter stage. Thus: VTEMP = 5000mV =
R4 R ðV þ VOFF Þ = 4 1200mV R3 P R3
R4 5000mV = ≈ 4:2 R3 1200mV
ð59Þ
ð60Þ
According to Eq. 60, we can use R3 = 1.1 KΩ and R4 = 4.7 KΩ. In Eq. 58, R5 can be taken as 10 KΩ. All three cascaded stages produce the following output voltage at VTEMP: VTEMP =
R4 R R1 þ R2 ðV þ VOFF Þ = 4 VT þ VOFF R3 P R3 R1
ð61Þ
In this equation, VTEMP becomes 0 V at T = -200°C (and VT = -8.14 mV). Similarly, at T = 200°C (and VT = 8.14 mV) VTEMP becomes approximately 5 V.
Designing the Electronics for a Light Level Measurement System Another interesting project with operational amplifiers is to measure the intensity of a light source with a photodiode. Once reverse biased, photodiodes produce high levels of reverse saturation current when exposed to light. An OSRAM SFH-213 is a popular photodiode with a spectral range of 400 nmm to 1100 nm, peaking at 900 nm. It has a relatively small dark (thermal) current, IDARK, of 1 nA and area of 1 mm2. Its light sensitivity of 550 mA/W makes this diode generate 5.5 μA of photo current (short circuit), IPH, when exposed to 10 W/m2 intensity light source within its spectral range. The reverse breakdown voltage of SFH-213 is 50 V per manufacturer’s datasheet. Therefore, reverse biasing this diode with –VCC = -15 V as shown in Fig. A.24 is
342
Appendix Designing Front-End Electronics
safe although this voltage level may also increase the dark current characteristics beyond what is specified in the datasheet. The pre-amplification stage in Fig. A.24 produces a gain proportional to the resistor R1 as shown in Eq. 62. VPH = R1 ðIPH þ IDARK Þ
ð62Þ
The resistor, RP, limits the current through the photodiode and also provides a reverse biased voltage less than VCC across its terminals in case the power supply voltage is greater than the breakdown voltage. VPH R1(IPH + IDARK)
VOUT
VDARK
R3 R1IPH R2
VCC
t
t
R1 R4 Light source
Incident light
VPH
R3
I4 R2 R2
RDARK IPH+ IDARK
AD7819
VOUT
8
LIGHTOUT
VDARK
RP
R3
ADC
-VCC Pre-amplification
Signal-conditioning
Fig. A.24 A circuit that measures light level (–VCC is used to bias the photodiode)
The second stage in Fig. A.24 is primarily used for signal conditioning such as elimination of the dark current component of the photodiode and possibly noise cancellation with some voltage amplification. This stage produces the following voltage gain: VOUT =
R3 ðV - VDARK Þ R2 PH
ð63Þ
Substituting Eq. 62 into Eq. 63 yields: VOUT =
R3 ½R ðI þ IDARK Þ - VDARK R2 1 PH
ð64Þ
In Eq. 31, if we tune the potentiometer such that: VDARK = R1 IDARK Then Eq. 64 can be reformed as:
ð65Þ
Appendix Designing Front-End Electronics
343
VOUT =
R3 R I R2 1 PH
ð66Þ
Using IPH = 5.5 μA and the maximum value of VOUT = 5 V, Eq. 66 can be rewritten as: R3 5V R = ≈ 909KΩ R2 1 5:5 × 10 - 6 A
ð67Þ
According to Eq. 67, selecting R2 = 1 KΩ, R1 = 27 KΩ produces R3 = 33 KΩ. From Eq. 65, the dark voltage becomes: VDARK = 27 × 103 × 1 × 10 - 9 = 27 μV
ð68Þ
From Eq. 62, VPH becomes approximately to be 0.15 V. If we allow I4 = 1 mA at the input of the second stage, R4 can be approximately equal to: R4 =
15V ≈ 15KΩ 1 × 10 - 3 A
ð69Þ
The potentiometer, RDARK, should be a small value compared to R4 such as 100 Ω. This way, values up to 100 μV can be produced at VDARK. The second schematic in Fig. A.25 for the same project is used if the reverse bias voltage for the photodiode is +VCC = 15 V. This configuration requires both amplification stages in Fig. A.24 to be inverting type. Both circuits use Analog Devices 8-bit ADC, AD7819.
VCC VOUT Light source
RP
R3 R1IPH R2
VCC
IPH+ IDARK
t
R1 Incident light
R3
R4 VPH VPRE
RDARK
I4 R2 R2
VOUT
AD7819
VDARK R3
ADC
Pre-amplification Signal-conditioning
Fig. A.25 A circuit that measures light level (+VCC is used to bias the photodiode)
8
LIGHTOUT
344
Appendix Designing Front-End Electronics
Designing Photo Detector Circuits The circuits in Figs. A.26 and A.27 are simple on/off circuits sensitive to light, and do not require any ADC to interface with the microcontroller. The sole purpose of these circuits is to create a hardware interrupt for the microcontroller or engage an electro-mechanical device based on light detection. VCC RPH
VCC IPH
IREF VPH
Light source RREF
Incident light
VREF
VOUT
Fig. A.26 Photo detector circuit with an operational amplifier
When the light is exposed to the photodiode in Fig. A.26, the voltage level at VPH drops with respect to VREF because the photo current through the diode, IPH, becomes much larger than the thermally-generated reverse saturation current, IDARK. Therefore, VPH = VCC –RPH IPH
ð70Þ
If we use the same photodiode, SFH-213, from the earlier design project with IPH = 5.5 μA (and negligible thermally-generated reverse saturation current), and assume VCC = 5 V, VPH = 3 V (although values less that 3 V and closer to 5 V are also acceptable design entries for VPH), then RPH becomes: RPH =
ðVCC - VPH Þ 5-3 = ≈ 363KΩ IPH 5:5 × 10 - 6
ð71Þ
When the light is off, IPH ≈ 0 A and VPH = VCC = 5 V. This means that VREF should be somewhere between VPH = 3 V and 5 V. Suppose VREF = 4 V and IREF = 1 mA. Then the maximum potentiometer value, RREF, becomes: RREF =
VCC 5 = = 5KΩ IREF 1 × 10 - 3
ð72Þ
Appendix Designing Front-End Electronics
345
If we adjust the tap on the potentiometer, RREF, such that VREF becomes 4 V, then Δv = VPH – VREF becomes Δv = 3 – 4 = -1 V when the light turns on, and VOUT = 5 V. When the light turns off, VREF is still at 4 V, but VPH becomes 5 V. This produces Δv = 5- 4 = +1 V, and VOUT becomes 0 V. The circuit in Fig. A.27 is an extension of the photo detector circuit in Fig. A.26, and is used to operate a small DC motor. In this figure, the DC motor spins as long as the light is on, but it turns off when the light is switched off. A Zener diode is used to set a reference voltage for the difference amplifier.
VCC RPH
IPH
VCC
VCC IZ VPH
Light source
RC
RZ
ICSAT
IBSAT VOUT
VZ
RB
Incident light
IM M
VM
Fig. A.27 Photo detector circuit operating a small DC motor
Assume that the power supply voltages are +VCC = 15 V and –VCC = 0 V. IPH = 5 μA when the light turns on, and the DC motor operates at a voltage of VM = 5 V at IM = 100 mA. When the light is on, assume VPH = 13 V. This produces: RPH =
ðVCC - VPH Þ 15 - 13 = = 400KΩ IPH 5 × 10 - 6
ð73Þ
When the light is off, the photodiode only generates negligible reverse biased current (IDARK), and VPH becomes VCC = 15 V. Therefore, VZ must be selected somewhere between VPH = 13 V and 15 V although voltages much lower then VPH = 13 V are also acceptable design entries. Thus, assume VZ = 14 V with IZ = 1 mA from the Zener diode datasheet. When the light is on, Δv = VPH – VZ, becomes Δv = 13 – 14 = -1 V, and the output of the operational amplifier, VOUT, becomes 15 V. This voltage should drive the NPN transistor into saturation. Thus: VOUT = RB IBSAT þ VBESAT þ VM
ð74Þ
VCC = RC ICSAT þ VCESAT þ VM
ð75Þ
346
Appendix Designing Front-End Electronics
Substituting VOUT = 15 V, VCC = 15 V, and the typical values of VBESAT and VCESAT into Eq. 74 and Eq. 75 yields: RB IBSAT = 15 - 0:8–5 = 9:2V
ð76Þ
RC ICSAT = 15–0:2–5 = 9:8V
ð77Þ
If 2N3904 is used in the circuit, then we can ignore the value of IBSAT compared to ICSAT. This produces IM = 100 mA ≈ ICSAT. Then from Eq. 77: RC =
9:8V ≈ 100Ω 100mA
ð78Þ
From the I-V characteristics of 2N3904, the NPN transistor goes into saturation at ICSAT = 100 mA when IBSAT ≥ 100 μA. Thus: RB =
9:2V ≈ 92KΩ 100μA
ð79Þ
When the light turns off, Δv = VPH – VZ = 15 – 14 = +1 V, and VOUT becomes 0 V. This voltage turns off the NPN transistor and therefore the DC motor.
Designing the Electronics for an Optoelectronic Tachometer Among all the other projects the optoelectronic tachometer design is the most demanding because it contains both analog and digital building blocks to complete the design. The analog part shown in Fig. A.28 is similar to Fig. A.25 except the incident light beam to the photodiode is chopped by a four-bladed propeller.
VPH VCC
R1(IPH+ IDARK ) VDARK
RP
Light source
clockVAR R3 R1IPH R2
t IPH+ IDARK
VCC R1
t R5
Incident light R5
VPH
VPRE
RDARK
R3
R4 I4 R2
clockVAR
R2 VDARK R3
Pre-amplification
Inversion
Signal-conditioning
Fig. A.28 Clock generation circuit for optoelectronic tachometer
Appendix Designing Front-End Electronics
347
The data path in Fig. A.28 amplifies and cleans the pulsed photo current, IPH, generated by the photodiode. The output of this circuit, clockVAR, indicates a variable clock signal, and is used to compute the prop rpm which varies in time. This variable but low frequency clock signal is tracked by a known reference clock, clockREF, oscillating at 1 MHz, in order to compute the unknown frequency at clockVAR. The circuit in Fig. A.29 shows two counters. The first counter, Pulse Counter, is a 16-bit wide counter and operates with the unknown clock frequency at clockVAR. The second reference counter, Ref Counter, operates with 1 MHz clock at clockREF, and it is 26 bits wide. The output of Ref Counter, CountOutREF, is connected to a decoder whose sole purpose is to detect the end of a minute long time period. This translates to generating logic 1 at OneMinute node for a binary value of 11-10010011-1000-0111-0000-0000 at CountOutREF or a waiting for a period of 6 × 107μsec before generating a Stop signal with the 1 MHz reference clock. When the end detection occurs, the pulse generated at OneMinute port stops both the Pulse and Ref Counters. The total number of counts at the output of Pulse Counter, CountOutPULSE, signifies the number of optical pulses received within one minute time interval. However, depending on the number of blades on the propeller, this number still needs to be divided by the number of blades on the prop. Therefore, a four bladed prop requires CountOutPULSE to be shifted to the right by two. The output of the shifter, RPMOUT, thus reads the number of revolutions of the fourbladed prop per minute. Reset Stop
Reset Pulse Counter
16
Ref Counter
Stop
clockVAR
26
CountOutREF
LSB
MSB
CountOutPULSE Divide by Blade
clockREF (1MHz)
16 RPMOUT
OneMinute
Fig. A.29 An optoelectronic tachometer architecture
Fig. A.30 shows the circuit schematic of the counters. After an external reset, the pulse and reference counter outputs become CountOutPULSE = 0 and CountOutREF = 0, respectively, and both counters immediately start counting
348
Appendix Designing Front-End Electronics
upwards using different clocks until they reach the 1 min mark. At the 1 min mark, the pulse received from the Stop input switches the port configuration of the 3-1 MUX from C-port to S-port, halting the count mechanism and putting both counters in idle mode. The counters stay in this state until they simultaneously receive an external reset. The digital circuits in Fig. A.29 can be implemented with discrete ICs from Texas instruments. However, it is best to consider a Field-Programmable-Gate-Array (FPGA) platform for implementations that require higher clock frequencies, especially when building timing-critical units such as a reference counter in a similar design. Fig. A.30 Pulse and reference counter circuit for the optoelectronic tachometer
+1
0 Reset Stop
R
S
C 26 D
clockREF Q
26 CountOutREF
Designing the Electronics for a Hall-Effect Tachometer The circuit in Fig. A.31 operates similar to the optoelectronic tachometer in the previous project but with a Hall-effect device in place. This project uses Allegro A1120 Hall-effect sensor. Every time the sensor is exposed to magnetic field, as small as 35Gauss, from a permanent magnet placed on a rotating disc in Fig. A.31, the NPN bipolar transistor in the device turns on and switches the output node, clockVAR, to 0 V.
Appendix Designing Front-End Electronics
349
VCC
VCC
0.1µF
RL
VCC
OUT
Hall device
Magnetic field
clockVAR VCC
H t Magnet
DC amplifier
Schmitt trigger
ground
ground
Allegro Hall Effect Device A1120
Fig. A.31 The clock generation circuit for a Hall-effect-based tachometer
When the magnetic field is removed from the sensor or drops below 25Gauss, the NPN transistor turns off and clockVAR is pulled up to VCC. This produces a clock waveform with a very brief duration at 0 V. A capacitor is added between VCC and ground to eliminate noise components in the power supply. Applying this clock signal to the Pulse Counter, and a 1 MHz reference clock to the Ref Counter in Fig. A.29 produces the same scenario described in the previous project. When 1 min elapses, CountOutREF produces a pulse at OneMinute port, and disables the counting mechanism in both the Pulse and Ref counters. The number of pulses at CountOutPULSE determines the rpm of the rotating disc. There is no need for a Divide by Blade unit, representing the number of blades in a prop, unless there is more than one permanent magnet on the rotating disc.
Index
A Accelerometer, 171–207, 219, 252, 263, 317 Analog-to-Digital Converters (ADC), 45, 50, 111–152, 157, 158, 169, 249, 264, 335, 337, 338, 341, 343, 344
B Brushless motor, 35, 267, 270, 313–317
D Data converters, vii, 111–169 Digital-to-Analog Converter (DAC), 1, 2, 52, 53, 65, 111, 112, 117, 118, 153–169, 250, 257, 261, 262, 264
Inter Integrated Circuit (I2C) interface, 61–65, 75
L LAT registers, 32, 38–42
M Microphone, 158, 249–264, 335–337 Motor control unit, 296
F Flash memory interface, 106 Front end electronics, 319–349
O Operational amplifiers, 115, 118, 264, 319–321, 327, 329–331, 341, 344, 345 Oscillator architecture, 25, 26 Oscillator configuration, 1–22, 70, 71, 98, 193, 261, 289 Output compare (OC) units, 225, 230, 231, 267, 293–303, 317
I I2C ports SCL, 61, 63–65, 75 SDA, 61–64 Image recognition camera, 207 Internal and external 16-bit processor oscillators, 1–3, 9, 12, 15, 19, 20, 25, 44, 67, 154, 159, 198, 224, 249, 254, 264, 277, 299 Internal and external memories to 16-bit microcontroller, 37, 77–108
P Parallel I/O ports, 36, 37 Peripheral Pin Select (PPS) system, 28–33, 71, 164, 221, 255 Pixy, 207–247, 263 PORT registers, 38–42 Pragma statements, 44, 70, 84, 101, 140, 193, 219, 285, 292 Pulse Width Modulation (PWM), 35, 131, 224, 225, 230, 267, 270–298, 301, 303, 304, 312, 316, 317
© The Editor(s) (if applicable) and The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 A. Bindal, Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers, https://doi.org/10.1007/978-3-031-27841-9
351
352 S Sensor electronic design, 319–330 Serial interface programming, 65–75 Serial Peripheral Interface (SPI), 57–60, 65–75, 77, 80, 84–89, 91, 98, 102–106, 108, 158, 164, 186–188, 195, 196, 198, 199, 201, 203, 207, 210, 220, 221, 223, 226, 227, 245, 249, 250, 253, 254 Servo, 35, 213, 214, 221, 224–227, 230, 231, 236–238, 245, 247, 267, 270, 292, 304–305, 317 16-bit processor architecture, 25 SPI ports SCK, 57–59, 65–67, 75 SDI, 57, 58, 66, 74, 75
Index SDO, 57, 58, 66, 67, 73–75 SS, 57 Stability interface, 171 Stepper motor, 305–312, 314, 316, 317 Synchronous Random Access Memory (SRAM), 77–91, 105, 109
T Timers, 2, 26, 37, 131, 219, 225, 226, 245, 252, 267–270, 279, 280, 285–288, 291, 296, 297, 301, 303 TRIS registers, 38–42