Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers 3031278402, 9783031278402

This textbook provides semester-length coverage of the basics of embedded programming to develop robotics-related projec

103 10

English Pages 364 [356] Year 2023

Report DMCA / Copyright

DOWNLOAD PDF FILE

Table of contents :
Preface
Contents
About the Author
Chapter 1: Oscillator Configuration
Reference
Chapter 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
Chapter 3: Serial Port
3.1 Introduction to Serial Peripheral Interface (SPI)
3.2 Introduction to Inter Integrated Circuit (I2C)
3.3 SPI interface
References
Chapter 4: External Memories
4.1 Synchronous Random Access Memory (SRAM)
4.2 Flash Memory
References
Chapter 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
5.6 Sample ADC Projects
5.7 Introduction to Digital-to-Analog Converter (DAC)
5.8 Digital-to-Analog Converter in dspic33fj128mc804
References
Chapter 6: Peripherals
6.1 Accelerometer
6.2 Pixy Camera
6.3 Microphone
References
Chapter 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
Appendix Designing Front-End Electronics
Operational Amplifier Properties
Voltage Amplifier Circuits for Sensors
Inverting Voltage Sensor Amplifier
Non-Inverting Voltage Sensor Amplifier
Isolation Circuit with Unity Gain
Voltage Sensor Amplifier with Signal and Reference Inputs
Summation Voltage Sensor Amplifier
Voltage Sensor Amplifier with Ungrounded Sensor Terminal
Trans-Resistance Amplifier Circuits for Sensors
Inverting Trans-Resistance Sensor Amplifier
Non-Inverting Trans-Resistance Sensor Amplifier
Analog Voltage Comparator
Schmitt Trigger
Design Projects
Designing the Electronics for an Analog Microphone
Designing Electronics for a Temperature Measurement System
Designing the Electronics for a Light Level Measurement System
Designing Photo Detector Circuits
Designing the Electronics for an Optoelectronic Tachometer
Designing the Electronics for a Hall-Effect Tachometer
Index
Recommend Papers

Designing Mobile Robot Interfaces with 16-bit Microchip Microcontrollers
 3031278402, 9783031278402

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

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