Introduction to C Programming with the ...

4 downloads 493 Views 8MB Size Report
the fundamentals concepts in programming and informatics as a junior/senior ... Among several other jobs, he has worked part-time for Indiana State University and ...... Available at: [Accessed 27.
Introduction to C Programming with the TMS320LF2407A™ DSP Controller

George Terzakis

ii

To my dad; we miss you. G.T.

iii

iv

About the author George Terzakis received the B.S. degree in Informatics from the University of Piraeus, Greece, in 1999. He received the M.S in Electronics & Computer Technology from Indiana State University, Terre Haute, IN, in 2002 and the M.S. in Robotics from the University of the West of England, Bristol, in 2010. He joined the Greek army in 2003 for a 12-month period of national service and during the period of 2005-2010, his main occupation involved teaching the fundamentals concepts in programming and informatics as a junior/senior high-school teacher. Among several other jobs, he has worked part-time for Indiana State University and the University of Patras, Greece, in topics concerning robotics and automation. His research interests are focused on robotics and relative disciplines.

v

vi

Contents Preface.............................................................................................................................................................. xvi Acknowledgements ...................................................................................................................................... xix 1. INTRODUCTION ..................................................................................................................... 1 1.1 The TMS320LF/LC240xA™ series of DSP Controllers by Texas Instruments .....................................................1 1.1.1 The TMS320LF2407A™ DSP Controller made by TI ...............................................................................1 1.1.2 Peripherals of the LF2407A Controller ..................................................................................................3 1.2

Programming the LF2407A in C ................................................................................................................4

1.3

What you need in order to read this book ...............................................................................................5

2. WRITING AND EXECUTING PROGRAM “HELLO” ......................................................... 7 2.1 Creating a first project with Code Composer Studio™ .....................................................................................7 2.1.1 Creating the C file(s) ..................................................................................................................................8 2.1.2 The missing details ....................................................................................................................................8 2.1.3 Putting it all together ..............................................................................................................................10 2.1.4 The moment of truth ...............................................................................................................................11 2.1.5 What do we expect to see? .....................................................................................................................13 2.2 How did we do it? ..........................................................................................................................................13 2.2.1 Initialization of the System Control and Status Registers 1, 2 (SCSR1 and SCSR2).....................................14 2.2.2 The Watchdog .........................................................................................................................................16 2.2.3 I/O Pin configuration ...............................................................................................................................18 2.3 Using C structures to access register bits .......................................................................................................20 2.4 The Linker Command File ...............................................................................................................................21 2.4.1 Sections generated by the C compiler .....................................................................................................21 2.4.2 The MEMORY clause in the Linker Command file.....................................................................................24 2.4.3 The SECTIONS clause in the Linker Command file ....................................................................................25

3. REFINING PROGRAM “HELLO” ........................................................................................28 vii

3.1 Beginning of execution ...................................................................................................................................28 3.1.1 The Reset interrupt Vector ......................................................................................................................29 3.2 Completing the “cvectors.asm” file ................................................................................................................31 3.2.1 The LF2407A Core Interrupts ...................................................................................................................31 3.2.2. Adding Declarations for Core Interrupt Service Routines 1-6 in C ...........................................................32 3.3 Building Register Structure Header files .........................................................................................................34 3.3.1 Creating an I/O Register Header File ........................................................................................................34 3.4. Completing the System Initialization Sequence ............................................................................................36 3.4.1 Configuring the System Control and Status Register 1 .............................................................................38 3.4.2 Configuring the Wait-State Generator Control Register (WSGR) ..............................................................39 3.5 Modifying the main() function .......................................................................................................................40 3.6 Adding files to the project ..............................................................................................................................41 3.7 Building and Debugging project “hello” .........................................................................................................42 3.7.1 Debugging the Program ...........................................................................................................................42 3.7.2 Setting up Breakpoints ............................................................................................................................43 3.7.3 Using the Watch Window ........................................................................................................................43 3.8. Summary .......................................................................................................................................................44

4. USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS .................................46 4.1 A brief introduction to Interrupts of the 2407A .............................................................................................46 4.1.1 The Peripheral Interrupt Expansion Controller (PIE) ................................................................................46 4.2 Using a General Purpose Timer ......................................................................................................................47 4.2.1 Project “Timer1” ......................................................................................................................................48 4.2.2 Timer 1 initialization ................................................................................................................................48 4.2.3 Timer 1 Interrupt Service Routines ..........................................................................................................55 4.3 Executing the Program ...................................................................................................................................60 4.4 Summary ........................................................................................................................................................61

5. THE EVENT MANAGERS ....................................................................................................64 5.1 Overview on the Event Managers ..................................................................................................................64 5.1 Using Timer 1 for Pulse Width Modulation ....................................................................................................65 5.1.1 Expanding “DSP24_Ev.h” .........................................................................................................................66 5.1.2 Modifying the Timer 1 Initialization function ...........................................................................................67 5.1.3 General Purpose Timer Control Register A...............................................................................................68

viii

5.1.4 I/O Pin Configuration ...............................................................................................................................69 5.1.5 The main() function .................................................................................................................................69 5.1.6 Observing the Pulse Width Modulated signal on the T1PWM/T1CMP pin ...............................................70 5.2 Using the Compare Units for Pulse Width Modulation in a new project........................................................72 5.2.1 Configuring Compare Unit 1 in EVA .........................................................................................................73 5.2.2 The Compare Control Register A (COMCONA) .........................................................................................75 5.2.3 The Compare Registers (CMPR1-6) ..........................................................................................................77 5.2.4 The Compare Action Control Registers (ACTRA and ACTRB) .....................................................................77 5.2.5 The Dead-band Timer Control Register A (DBTCONA) ..............................................................................78 5.2.6 PWM Initialization Routine ......................................................................................................................80 5.2.7 I/O Initialization .......................................................................................................................................83 5.2.8 Interrupt Service Routines .......................................................................................................................84 5.2.9 Compiling and executing .........................................................................................................................85 5.3 Using the EVA Capture Units ..........................................................................................................................88 5.3.1 Using the Capture Units for Rising/Falling edge detection in a new project .............................................90 5.3.2 Defining Structures for the Capture Unit Registers ..................................................................................91 5.3.3 EVA Interrupt Mask and Flag Registers B and C .......................................................................................98 5.3.4 Capture Control Register A and Capture FIFO Status Register A ............................................................. 100 5.3.5 Capture FIFO Registers .......................................................................................................................... 102 5.3.6 Initializing Capture Units 1 and 2 ........................................................................................................... 103 5.3.7 Core INT4 and Capture 1 and 2 Interrupt Service Routines .................................................................... 105 5.3.8 Configuring Capture I/O Pins 1 and 2 ..................................................................................................... 110 5.3.9 Executing the Program .......................................................................................................................... 111 5.4 Conclusions/Suggestions ..............................................................................................................................113

6. THE ANALOG TO DIGITAL CONVERTER .................................................................... 115 6.1 Overview of the Analog to Digital Converter ...............................................................................................115 6.1.1 The Sequencers ..................................................................................................................................... 115 6.2 Using the ADC in Cascaded Sequencer Mode ...............................................................................................117 6.2.1 Creating header and C files for the ADC Registers .................................................................................. 118 6.2.2 The ADC Control Register 1 (ADCTRL1) .................................................................................................. 121 6.2.3 The ADC Control Register 2 (ADCTRL2) .................................................................................................. 123 6.2.4 Autosequence Status Register (AUTO_SEQ_SR) ..................................................................................... 125 6.2.5 The Maximum Conversion Channels Register (MAXCONV) .................................................................... 126 6.2.6 The ADC Input Channel Select Sequencing Control Registers (CHSELSEQ n) ............................................ 127 6.2.7 ADC Conversion Result Buffer Registers (RESULTn) ................................................................................ 128 6.2.8 Initializing the ADC in Cascaded Sequencer Mode ................................................................................. 128 6.2.9 The ADC High Priority Interrupt Service Routine .................................................................................... 131 6.2.10 I/O initialization ................................................................................................................................... 133 6.2.11 The main() function ............................................................................................................................. 133 6.2.12 Executing the program ........................................................................................................................ 134

ix

6.2. 13 Verifying expected time lapse between consecutive conversion sessions ........................................... 137 6.3 Using the ADC in Dual Sequencer Mode ......................................................................................................138 6.3.1 A new project for ADC operation in Dual Sequencer Mode.................................................................... 139 6.3.2 Configuring the ADC for Dual Sequencer Operation Mode .................................................................... 139 6.3.3 The ADC Interrupt Service Routine in Dual Sequencer Mode .................................................................140 6.3.4 The main() function ............................................................................................................................... 142 6.3.5 Executing the program .......................................................................................................................... 143 6.4 Summary/Conclusions .................................................................................................................................144

7. THE SERIAL COMMUNICATIONS INTERFACE .......................................................... 146 7.1 Overview of the Serial Communications Interface .......................................................................................146 7.2 A Program to Transmit and Receive Characters with the SCI .......................................................................147 7.2.1 Creating a new header file for SCI register structures ............................................................................ 147 7.2.2 The SCI Communication Control Register (SCICCR) ................................................................................ 150 7.2.3 The SCI Control Register 1 ..................................................................................................................... 151 7.2.4 The SCI Control Register 2 ..................................................................................................................... 153 7.2.5 The SCI Receiver Status Register ............................................................................................................ 153 7.2.6 The SCI Priority Register ........................................................................................................................ 154 7.2.7 Baud-Select Registers (SCIHBAUD – SCILBAUD) ..................................................................................... 155 7.2.8 Receiver Buffer Register and Transmitter Buffer Register ...................................................................... 157 7.2.9 The main() function of project “sci” ....................................................................................................... 157 7.2.10 Initialization of Serial Communications (file “DSP24_Uart.c”) .............................................................. 158 7.2.11 I/O Configuration.................................................................................................................................162 7.2.12 Interrupt Service Routines ................................................................................................................... 162 7.2.13 Program Execution .............................................................................................................................. 164 6.3 Summary ......................................................................................................................................................166

8. THE SERIAL PERIPHERAL INTERFACE ...................................................................... 168 8.1 Overview of the Serial Peripheral Interface .................................................................................................168 8.2 A Program to Transmit characters with the SPI using the DSP as Master ....................................................169 8.2.1 Creating a new header file for the SPI registers ..................................................................................... 169 8.2.2 The SPI Configuration Control Register (SPICCR) .................................................................................... 172 8.2.3 The SPI Operation Control Register (SPICTL) .......................................................................................... 173 8.2.4 The SPI Status Register (SPISTS) ............................................................................................................. 176 8.2.5 The SPI Baud Rate Register (SPIBRR) ...................................................................................................... 177 8.2.6 The SPICLK signal in terms of the CLKOUT signal and the value in SPIBRR .............................................. 177 8.2.7 The SPI Priority Register (SPIPRI) ........................................................................................................... 179 8.2.8 The SPI Receive and Transmit Buffer Registers (SPIRXBUF and SPITXBUF) ............................................. 179 8.2.9 The SPI Emulation Buffer Register (SPIRXEMU) ...................................................................................... 179

x

8.2.10 Initialization of the SPI ......................................................................................................................... 180 8.2.11 Sending a character ............................................................................................................................. 182 8.2.12 I/O Configuration.................................................................................................................................182 8.2.13 The main() function ............................................................................................................................. 183 8.2.14 SPI Interrupt Service Routines ............................................................................................................. 184 8.2.15 Building and Executing ........................................................................................................................ 185 8.3 Summary/Conclusions .................................................................................................................................187

9. THE CONTROLLER AREA NETWORK (CAN) MODULE .......................................... 189 9.1 Overview of the CAN Bus Protocol ...............................................................................................................189 9.1.2 The CAN Physical Layer .......................................................................................................................... 189 9.1.3 The CAN lines (CANH and CANL) ............................................................................................................ 190 9.1.3 Exchanging messages through the CAN bus using Data and Remote Request Frames ........................... 191 9.1.4 Placing frames on the CAN bus .............................................................................................................. 194 9.1.5 The Bit Stuffing Rule .............................................................................................................................. 196 9.1.6 Error and Overload Frames.................................................................................................................... 196 9.1.7 States of a CAN node ............................................................................................................................. 196 9.1.8 Bit Timing and Synchronization ............................................................................................................. 199 9.2 Overview of the CAN Controller ...................................................................................................................200 9.2.1 CAN Mailboxes ...................................................................................................................................... 201 9.3 Using the CAN module to Transmit and Receive Data frames .....................................................................202 9.3.1 Creating a new header file for the CAN registers ................................................................................... 203 9.3.2 Message Identifier for High-Word Mailboxes 0-5 (MSGIDnH) ................................................................ 213 9.3.3 Message Identifier for Low-Word Mailboxes 0-5 (MSGIDnL) .................................................................214 9.3.4 Message Control Field (MSGCTRLn) ....................................................................................................... 214 9.3.5 The Local Acceptance Mask Register 0 and 1 High Word (LAM0_H and LAM1_H).................................. 214 9.3.6 The Local Acceptance Mask Register 0 and 1 Low Word (LAM0_L and LAM1_L) .................................... 215 9.3.7 Mailbox Buffers ..................................................................................................................................... 215 9.3.8 The Mailbox Direction/Enable Register (MDER) ..................................................................................... 216 9.3.9 The Transmit Control Register (TCR) ...................................................................................................... 217 9.3.10 The Receive Control Register ............................................................................................................... 218 9.3.11 The Master Control Register (MCR) ..................................................................................................... 219 9.3.12 The Bit Configuration Register 2 (BCR2) ............................................................................................... 221 9.3.13 The Bit Configuration Register 1 (BCR1) ............................................................................................... 221 9.3.14 The Error Status Register (ESR) ............................................................................................................ 223 9.3.15 The Global Status Register (GSR) ......................................................................................................... 225 9.3.16 The CAN Error Counter Register (CEC) .................................................................................................226 9.3.17 The CAN Interrupt Flag Register (CAN_IFR) .......................................................................................... 226 9.3.18 The CAN Interrupt Mask Register (CAN_IMR) ...................................................................................... 227 9.3.19 Initialization of the CAN module .......................................................................................................... 228 9.3.20 A Routine for Transmission .................................................................................................................. 234 9.3.21 I/O Configuration.................................................................................................................................235

xi

9.3.22 CAN Interrupt Service Routines ........................................................................................................... 235 9.3.23 The main() function ............................................................................................................................. 237 9.3.24 Building and Executing ........................................................................................................................ 238 9.3.25 Tracking the data frame on the transmission line ................................................................................ 239 9.4 Summary/Conclusions .................................................................................................................................242

10. THE DISCRETE FOURIER TRANSFORM ..................................................................... 244 10.1 Overview on Fourier Analysis .....................................................................................................................244 10.1.1 Complex Numbers as a Representation of Phase and Magnitude ........................................................ 244 10.1.2 Fourier Analysis ................................................................................................................................... 245 10.1.3 The Fourier Series ................................................................................................................................ 247 10.1.4 The Time-Continuous Fourier Transform ............................................................................................. 249 10.1.5 The Fourier series for Discrete-Time Signals (Discrete Fourier Transform) ........................................... 250 10.1.5 The DFT algorithm ............................................................................................................................... 251 10.2 A Program to perform signal Analysis using the simple DFT ......................................................................252 10.2.1 Modifying the Linker Command File .................................................................................................... 252 10.2.2 Initialization of the ADC ....................................................................................................................... 254 10.2.3 Initialization of Timer 1 for Pulse Width Modulation ........................................................................... 255 10.2.4 Initialization of the SPI ......................................................................................................................... 259 10.2.5 Global Data and DFT Routines ............................................................................................................. 261 10.2.6 The ADC Interrupt Service Routine ...................................................................................................... 263 10.2.7 I/O initialization ................................................................................................................................... 265 10.2.8 The main() function ............................................................................................................................. 265 10.2.9 Program Execution .............................................................................................................................. 266 10.3 The Fast Fourier Transform (FFT) ...............................................................................................................267 10.3.1 How the FFT works .............................................................................................................................. 268 10.3.2 Forward/Inverse FFT............................................................................................................................ 272 10.3.3 Adding FFT routines in the application.................................................................................................274 10.3.4 Signal Transposition Routine ............................................................................................................... 275 10.3.5 Routines for Forward/Inverse FFT Computations ................................................................................. 278 10.3.6 Modifying the main() function ............................................................................................................. 285 10.3.7 Program Execution .............................................................................................................................. 286 10.4 Conclusions/Suggestions ............................................................................................................................286

11. DIGITAL FILTERS ............................................................................................................. 289 11.1 Overview on Digital Filtering ......................................................................................................................289 11.1.1 Convolution ......................................................................................................................................... 289 11.1.2 Impulse, Frequency and Step Response of LTI Systems ........................................................................ 291 11.1.3 Implementation of Digital Filters ......................................................................................................... 293 11.1.4 Calculating the Output of a FIR filter .................................................................................................... 294

xii

11.1.5 Calculating the Output of an IIR filter .................................................................................................. 295 11.2 Implementing FIR filters .............................................................................................................................300 11.2.1 Moving Average Filters ........................................................................................................................ 300 11.2.2 Windowed-Sinc Low-Pass Filters.......................................................................................................... 302 11.2.3 Windowed-Sinc High-Pass Filters ......................................................................................................... 306 11.2.4 Windowed-Sinc Band-Pass Filters ........................................................................................................ 307 11.2.5 Peripheral Initialization........................................................................................................................ 309 11.2.6 Input shifting and Convolution ............................................................................................................ 312 11.2.7 Putting it all together .......................................................................................................................... 313 11.2.8 Interrupt Service Routines ................................................................................................................... 316 11.2.9 The main() function ............................................................................................................................. 317 11.2.10 Program execution ............................................................................................................................ 319 11.3 Implementing IIR filters ..............................................................................................................................326 11.3.1 Transforming Continuous Filters into Discrete Filters .......................................................................... 327 11.3.2 Designing a 2nd order Low-Pass Butterworth Filter .............................................................................. 330 11.3.3 Designing a 2nd order Butterworth High-Pass Filter .............................................................................. 332 11.3.4 Calculating the Output of the filter ...................................................................................................... 333 11.3.5 Putting it altogether ............................................................................................................................ 335 11.3.6 The main() function ............................................................................................................................. 338 11.3.7 Program Execution .............................................................................................................................. 339 11.4 Conclusions/Suggestions ............................................................................................................................343

12. TOPICS OF SPECIAL INTEREST .................................................................................... 345 12.1 Creating C-callable Assembly functions ......................................................................................................345 13.1.1 The Components of the C2xx CPU ....................................................................................................... 345 12.1.2 Assembly Language of the LF2407A ..................................................................................................... 348 12.1.3 Creating C-callable Assembly function Templates with TI’s Code Composer Studio™ .......................... 351 12.1.4 A few Tips for your C-callable Assembly Template ............................................................................... 367 12.2 More about the Watchdog Timer ...............................................................................................................367 12.2.1 Overview of the Watchdog Timer ........................................................................................................ 368 12.2.2 Using the Watchdog in a Program ....................................................................................................... 369

Bibliography ...................................................................................................................................................374 Sources ............................................................................................................................................................375 Appendix A: Header and C files for EVA, EVB .......................................................................................378 Structure Definitions (“DSP24_Ev.h”) ............................................................................................................. 378 Register Declarations (“DSP24_Ev.c”) ............................................................................................................. 388

Appendix B: Header and C files for the LF2407A ISRs ........................................................................392 Declarations of Interrupt Service Routines (“DSP24_DefaultISR.h”) ............................................................... 392 Implementations of Interrupt Service Routines (“DSP24_DefaultISR.c”)......................................................... 394

xiii

xiv

xv

Preface

My first experience with the C2x generation of DSP controllers by Texas Instruments was with the TMS320F2812™ in the context of a graduate course related to digital signal processing. The first impression, for someone who has an average amount of experience in embedded programming, is that this product can be looked upon in two ways: Either as a hi-fi microcontroller or as a very specialized CPU in terms of certain mathematical operations commonly employed in DSP algorithms. My problem was to select a proper course of action towards these two directions, in order to broaden my overall knowledge on the DSP controller. I realized that the best way to make a “smooth” start into understanding the F2812 was to initially face it as a typical microcontroller and program it in a high-level language (i.e., C) to perform simple or relatively elaborate tasks involving I/O operations. I downloaded the tidcs set of C/C++ header file libraries by TI and used several available examples to “break” my way through the preliminary steps. During that period, I came across the F2812’s 16-bit fixedpoint counterpart by Texas Instruments, the TMS320LF2407A™. The two controllers have an almost identical set of peripherals and, to program them in C, was essentially a similar task. Since the LF2407A did not have a corresponding set of C/C++ libraries, I used the tidcs header files as templates to create a set of structures for its memory-mapped registers. During this process, several architectural characteristics of the C24x CPU would gradually emerge and the information found in application notes and technical manuals that initially seemed to be scattered pieces of a puzzle would eventually start to fall into place. This book is a compilation of methods and examples placed in an order that could take a student without any prior knowledge on the TMS320™ series by Texas Instruments through a fast introduction to C programming with the controller, while several other concepts and issues will be gradually addressed in the context of examples. Additionally, the text “points” to certain technical manuals and application notes that provide the full extent of the information required to comprehend the topic under examination. Assembly language is generally avoided until Chapter 12. It is my opinion that one should be already acquainted with the essentials of the controller before moving on to optimizing code by making use of the specialized instruction set of the C24x CPU core. The examples related to DSP (fast Fourier transform, digital filters) in Chapters 10 and 11 are all implemented in C. These examples merely intend to demonstrate the abilities of the xvi

peripherals introduced in the previous chapters, as well as the possibility of optimizing their performance by considering the advantages offered by the instruction set of the C24x core. To conclude, this book is intended for students who wish to have a fast introduction with the TMS320™ series by Texas Instruments, using the TMS320LF2407A™ DSP controller. It is a compilation of solutions to problems that usually appear during the first stages of one’s “acquaintance” with the DSP controller and its peripherals. Moreover, the code examples presented throughout the text are intended to illustrate the basics and provide the incentive for further investigation on the issue at hand, rather than to give a “clean-cut” solution to a problem with specified parameters. In overall, this book is more-less the introductory course I would have chosen for myself in order to understand the essentials of a DSP embedded platform and I am hoping that it will serve as an equally useful guide to students and aspiring engineers.

George Terzakis [email protected] Bristol, United Kingdom

xvii

xviii

Acknowledgements

I would like to thank Julie Van Haren, Director of Communications – Embedded Processing, Texas Instruments, for her useful advises regarding the proper use of Texas Instruments trademarks in literature. I would also like to thank Manus and Jo McParland for devoting some of their time in making sure that the text is devoid of unnecessary pretentious language and that it is articulate and intelligible to the reader.

xix

xx

INTRODUCTION

1

Introduction

1.1 The TMS320LF/LC240xA™ series of DSP Controllers by Texas Instruments The Texas Instruments TMS320LF/LC240xA™ digital signal processor controller is a “nicely wrapped” package that includes a high-speed CPU core suitable for digital signal processing applications, combined with the typical set of microcontroller peripherals in a single-chip package (Figure 1-1). Performances of up to 40 million instructions per second (40 MIPS), is one of the greatest assets of the TMS320LF/LC240xA when compared to traditional 16-bit microcontrollers. The 240xA core uses 16-bit fixed point arithmetic with a wide variety of instructions specifically suited to accommodate the necessary calculations required in digital processing applications. 1.1.1 The TMS320LF2407A™ DSP Controller TI’s TMS320LF2407A™, henceforth referred to as LF2407A or 2407A throughout this book, is the classic representative of the 240xA series. The LF2407A is part of the C24x generation and member of the C2000™ platform of fixed point DSPs; the architectural similarities of these processors make software highly portable between them, while controller peripherals are practically identical in several aspects. Understanding the LF2407A is, in fact, the key to “unlocking the secrets” of all other members of the C2000 platform and possibly, many other DSP products by Texas Instruments. The broader TMS320™ family includes the C1x, C2x, C20x, C24x, C5x, C54x, and C6x generations of fixed-point DSPs; additionally, the C3x and C4x floating-point DSPs; finally, the C8x multiprocessor DSPs. Devices within a generation are identical in terms of CPU architecture, while they present slight variations in the corresponding configurations of memory and peripherals. The naming conventions applied in the C code examples throughout this book are suited to follow the standards of C libraries of other DSP models in the C2000 platform (i.e., the TMS320F2812™). You may be surprised to see how easily the code introduced in the next chapters will apply to other DSP controllers (such as the TMS320F2812™) with minor differences, regardless of CPU architectural incompatibilities (e.g., 16-bit as opposed to 32bit).

1

INTRODUCTION

Figure 1-1. 240xA CPU Core and Peripherals single-chip package1.

1

TMS320LF/LC240xA DSP Controllers Reference Guide-System and Peripherals (Texas Instruments)

2

INTRODUCTION

1.1.2 Peripherals of the LF2407A Controller The LF2407A combines the high-performance CPU core with a set of peripherals acting as the “heavy artillery” to meet interfacing requirements for the most demanding of problems in terms of digital signal processing, communications and general purpose I/O operations. The set of peripherals on the LF2407A includes: The Event Managers, incorporating Timers and PWM generators. The Controller Area Network (CAN) Module. The Analog to Digital Converter. The Serial Peripheral Interface (SPI) for synchronous serial communications. The Serial Communications Interface (SCI) - asynchronous serial port (universal asynchronous receiver and transmitter − UART). The Watchdog timer. General bi-directional digital I/O (GPIO) pins. In the next chapters, each of the above peripherals will be introduced with simple examples and several configurations will be discussed. In terms of pure DSP applications, the peripherals commonly employed are the event managers and the ADC. The event managers. These peripherals include a set of modules to facilitate creation of pulse width modulated signals and capture rising/falling edges in pulses. In the “heart” of the event managers, lie the general purpose timers, providing clocking to the modules of the device; they may also be used to synchronize the occurrence of events in our programs. The event managers are highly configurable to the last detail, with an extensive list of configuration/control registers. The CAN module. The controller area network module implements the multi-master CAN bus communications protocol interface with a set of six mailboxes. The Analog to Digital Converter. The ADC is used to sample analog signals and produce the corresponding digital value stored in a 10-bit integer result. Arguably, it is one of the most significant peripherals on the LF2407A. It utilizes two sequencers as finite state machines that synchronize the sampling process. The Serial Peripheral Interface. The SPI is used for synchronous master-slave high speed serial communications. Typically, the SPI is used for communications with external devices such as LCDs and Digital to Analog Converters. The Serial Communications Interface. The SCI implements typical asynchronous serial communications (UART). Typical applications of the SCI include communications with other 3

INTRODUCTION

controllers, or a PC. The designated lines for reception (RX) and transmission (TX) are not level-shifted on the DSP board (i.e., they operate at 0-3.3V). The Watchdog Timer. The watchdog is essentially a timer, acting as a safety precaution against possible program locks in endless loops. When enabled, the watchdog increases an internal 8-bit counter using a clocking signal running at a sub-multiple frequency of the CPU clock signal. The program should be able to reset the counter before an overflow occurs; if, for any reason (which may possibly be an execution “hung”), the program fails to reset the watchdog in time, the counter will overflow and a system reset will be asserted. The General bi-directional I/O pins. The LF2407A has a set of general I/O pins organized in ports A, B, C, D, E and F. Most of the I/O pins on the LF2407A are multiplexed with other devices (e.g., general I/O pin A6 is multiplexed with the PWM1 pin) and must be configured prior to use, either for their primary (non - general I/O) or secondary (general I/O) function. Moreover, general I/O pins can be configured either as input or output.

1.2 Programming the LF2407A in C Traditionally, the 2407A does not come with C header files regarding its set of peripherals. Most of the existing code examples are developed in assembly. There are a great number of reasons why a DSP should be programmed in assembly and all of them have to do with performance in terms of execution time. However, using a high level language to initialize the required controller devices in a program should not interfere with performance, but rather only with the program size, which should not be a major issue these days. The following chapters take the reader throughout all peripherals on the 2407A with a few simple examples developed in C regarding each case. Since there are no libraries to work with, the text will guide the reader through the process of creating a set of header files involving the registers of the corresponding peripherals. It may be extra work, but it is advantageous; the process of creating these files can be a nice exercise for memorizing and/or highlighting the names of registers related to the initialization of every device on the DSP. Moreover, the naming conventions for typical header files, registers and functions (interrupt service routines and system initialization functions) are chosen so that they match the ones used in the tidcs library provided by Texas Instruments for the TMS320F2812. Given that the DSP controllers of the C2000 platform (and, possibly, ones that belong to other generations and platforms) share most of their peripherals, you will find that your code can be easily portable to other controllers with very few modifications. As mentioned earlier, the LF2407A is the gateway to understanding a broader set of DSP products by Texas Instruments. The overall gain is that in the end of the day, the reader should have a very good notion of what kind of problems one may be faced with, regarding initialization and use of peripherals, not 4

INTRODUCTION

only on the LF2407A, but on other DSPs, while building a useful set of code templates for his/her programs. The demonstration examples (fast Fourier transform, FIR and IIR filters) are all purely implemented in C; they are intended to demonstrate merely a fraction of the issues that an engineer/student may be faced with, when trying to implement the relative algorithms for commercial use. Different approaches in the implementation are recommended and discussed; including the use of C-callable assembly code to improve performance by taking advantage of the LF2407A specialized instruction set in terms of typical DSP computations. Regardless of whether the programs presented in this book are ideally suitable for solutions to many problems in the field, they merely serve as an introduction to the relative topics. The main goal is to suggest and discuss solutions in a comprehensive and coherent manner for the reader, without expecting prior extensive knowledge, either on embedded programming or signal processing in general.

1.3 What you need in order to read this book Reading through the first nine chapters does not require extended programming and/or digital signal processing skills. However, a minimum acquaintance with C language and computer architecture will be the prerequisites for the early chapters at least. Every new concept comes with an example, following a short discussion on the issues presented in that example. Readers who may have a certain experience in embedded programming will probably have a head-start; the concepts introduced here, are somehow “deviant” from the typical programming scheme in which, for example, a computer science student may be accustomed to and it may take a while until he/she becomes fully adjusted. Finally, a certain background on mathematics and control theory will be required for Chapters 10 and 11. It is advisable to consult and cross-check every new concept with the technical manuals provided by Texas Instruments and with the literature presented in the bibliography and sources sections of this book.

5

INTRODUCTION

6

WRITING AND EXECUTING PROGRAM “HELLO”

2

Writing and Executing Program “hello”

2.1 Creating a first project with Code Composer Studio™ We better quickly jump-start your interest with the 2407A with the old-fashioned “hello” program. In fact, the programming approach involves the all times classic code structure; we have a main() function, a few variables and a main loop. Sounds familiar? Start Code Composer Studio™ IDE by Texas Instruments and create a new project.

Figure 2-1. Creating a new project.

In the first step you will be asked to name the project, choose a suitable folder and a target. Type a name (i.e. “hello”) and create a folder in windows explorer to store your project. Preferably, create your folder without any spaces or special characters and make sure the path does not include extended names (e.g., “Documents and Settings”); the linker version may have an issue with these kinds of names and it may produce errors without any apparent reason. Choose “TMS320C24XX” as target.

Figure 2-2. One step away from project “hello”.

7

WRITING AND EXECUTING PROGRAM “HELLO”

2.1.1 Creating the C file(s) Like in any other C/C++ IDE, you will have to create your .c or .h files and thereafter, add them to the project. Let us create a file named “hmain.c” from File New Source File. If all went nicely, you should be able to work on a new editor window for the file you just created. Type the following code: #define #define #define #define

MCRA PADATDIR WDCR SCSR2

(volatile (volatile (volatile (volatile

unsigned unsigned unsigned unsigned

int int int int

*)0x7090 *)0x7098 *)0x7029 *)0x7019

void main() { unsigned int i, temp, temp1; *SCSR2 = (*SCSR2 | 0x000B) & 0x000F; *WDCR

= 0x00E8; // disable the watchdog

*MCRA &= 0xFFFC; // set Port A General Pins 0 and 1 // to General I/O function *PADATDIR|=0x0303; // Port A0, A1 bits output and 'high' /* main loop */ while (1) { for(i=0;i EXTPROG PAGE 0 .cinit: > EXTPROG PAGE 0 .const: > B1 PAGE 1 .switch: > EXTPROG PAGE 0 .bss: > EXTDATA PAGE 1 .stack: > DSARAM PAGE 1 .sysmem: > B1 PAGE 1

*/ /* /* /* /* /* /* /*

initialized */ initialized */ initialized */ initialized */ uninitialized */ uninitialized */ uninitialized */

/* Sections declared by the user */ /* vectors:

>

VECS

PAGE 0

*/ /* initialized */

}

In File New Source File create a file named “2407_cmd.cmd” and save it in folder “hello”. Notice highlighted section vectors mapped to VECS in PAGE 0; the allocation is commentedout. As stated in the comments, this section is NOT created by the C compiler, still it should be included in most applications, since it contains the core interrupt vector table of the 2407A; for now, keep it cased in comments. We will be going through the details of this command file shortly after we build and execute our project. Library file “rts2xx.lib”. Although upon the creation of the project we specifically stated a target (i.e., TMS320CXX), this is not enough for the compiler to produce targetspecific object files. File “rts2xx.lib” must always be added to the project when C language is 9

WRITING AND EXECUTING PROGRAM “HELLO”

used, otherwise the compiler will not know how to generate assembly instructions, allocate variables, emulate floating point arithmetic, perform data and program addressing, etc. You should be able to locate “rts2xx.lib” in “./C2400/cgtools/lib” starting from the Code Composer Studio™ root directory (usually, “C:\CCStudiocx.x”). 2.1.3 Putting it all together So, now we have a C file, a linker command file and a library file to add to the project. Just go Project Add Files to Project, or right-click on the project’s name in the project browser window floating on the left side of the IDE and choose Add Files to Project.

Figure 2-3. Adding files to the project.

Make sure to select the appropriate extension every time. The IDE will distribute .cmd and .c (as well as .asm, if any) under the Source tree node, whereas the .lib can be found under Libraries. If all went okay, your project browser window should look like the one in Figure 2-4. Do not forget to save your project using menu Project Save.

Figure 2-4. Project “hello” browser window; .cmd, .c, .lib files added.

10

WRITING AND EXECUTING PROGRAM “HELLO”

2.1.4 The moment of truth If you got as far as this, well then… give it a try! Hit Rebuild all on the toolbar and enjoy a successful compilation and linking message! If otherwise, check your code and command file for typos. Don’t worry about the warning; you simply need to specify default stack size; for now, it will have no impact in execution.

Figure 2-5. Successful build message.

Downloading the program to the LF2407A. Now we are halfway there! It is time to download and execute. Connect your DSP to the parallel port and power it up! Go to Debug Connect. If all went okay, you should be connected to the DSP. The cable icon on the left side of the status bar should be green, while the light next to it should be yellow, with a ”HALTED” indication to the right, meaning that the DSP is not executing at the moment (Figure 2-6).

Figure 2-6. Cable icon green color indicates connected target.

Notice that a new window titled “disassembly” opens automatically upon connection. It contains the next assembly instruction pointed to by the instruction register (otherwise known as program counter-PC). We may execute step-by-step, but that’s not what we want right now. Go File Load Program and choose “hello.out” from the “debug” folder inside your project folder (the IDE creates this folder during the build process to store the output file).

Figure 2-7. Load Program dialog.

11

WRITING AND EXECUTING PROGRAM “HELLO”

If the download went okay, you should be able to see the next assembly instruction right after a line marked with label _c_int_0 on the disassembly window.

Figure 2-8. Disassembly window shortly after downloading “hello.out”.

You can rest assured that your program is somewhere there. No need to try to understand complicated RISC assembly at the moment, but keep in mind that these instructions precede your code; in order to move the instruction pointer at the beginning of the C code, just do Debug Go Main. Now, you should be able to see the listing of “hmain.c”, right at the beginning of the main() function (notice the yellow arrow denoting the next command pending execution). Now you may debug your program in the typical ways by either stepping over (F10) or into (F11); of course, you may even run it (Debug Run).

Figure 2-9. Suspended execution at the beginning of main().

12

WRITING AND EXECUTING PROGRAM “HELLO”

2.1.5 What do we expect to see? Time to learn the truth about program “hello”; we have configured I/O pins 0 and 1, to general I/O function (most pins on the DSP are multiplexed so they may have different configurations). This time we are using them as outputs in order to toggle their state inside the main loop. You can “read” the DSP’s “hello” signal using an oscilloscope on pins A0 and A1. Figure 2-10 shows how to locate the pins on the board as well as the location of the reference ground. You may find the schematics of the DSP board in eZdsp™ LF2407A - Technical Reference by Spectrum Digital.

Figure 2-10. Locations of Ground, IOPA0 and IOPA1 on the DSP board I/O header1.

As shown in Figure 2-10, pins 3 and 4 (A0 and A1) have multiplexed functions. They can either be general I/O, which is the case this time, or SCITXD (serial transmit) and SCIRXD (serial receive). Use pin 40 as common ground reference. You may use Debug Run/Halt to check the progress of the program’s execution. Normally, the code should stop (something otherwise called “emulation suspension”) somewhere inside the while statement. If not, then something went horribly wrong and your DSP is running assembly instructions lost in program memory space! If that is the case, relax, take a deep breath and do Debug Reset CPU; check the code for errors, compile and download again. Do NOT reset the DSP after program download.

2.2 How did we do it? Okay, the program runs. We saw the DSP “greetings” through PORTA I/O pins 0 and 1; how exactly did we do it? Let us check the code line by line: #define MCRA 1

(volatile unsigned int *)0x7090

eZdsp™ LF2407A– Technical Reference (Spectrum Digital)

13

WRITING AND EXECUTING PROGRAM “HELLO”

Unfortunately for many C worshipers and generally, people who like a quick start on embedded programming, the LF2407A does not come with C header files or libraries in general. This is not bad news necessarily! On one hand, you may have to do a lot of preliminary work, but on the other hand, you are given the chance to create your own libraries and have complete low-level control of the programs you create. In order to initialize any device and/or peripheral on the LF2407A, we have to access the relative control and configuration registers. The great news is that almost every register (excluding certain core and I/O memory mapped registers) is mapped to a specific memory address! This way, we may access them using C pointer variables. Therefore, knowing that MCRA register is mapped to memory address 0x7090, we may define that “MCRA” in our code will always be a “nickname” for a pointer of type unsigned int to volatile content stored in address 0x7090. A bit confusing, but classic C programmers may already be enjoyng it. You can always create unsigned int pointer variables to volatile content of memory locations corresponding to registers used in your program. The volatile keyword should always be used when defining variables that point to memory addresses corresponding to registers; it implies that the content of the memory location can be altered by external sources to our program (e.g., an interrupt flag is usually set outside the program). Accordingly, the following define directives correspond to the rest of the registers accessed in the program. #define PADATDIR #define WDCR #define SCSR2

(volatile unsigned int *)0x7098 (volatile unsigned int *)0x7029 (volatile unsigned int *)0x7019

Now we know that registers MCRA, PADATDIR, WDCR and SCSR2 are used in the “hello” program. At this point you better make sure that you have already downloaded TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals; try to keep it available for more information on peripherals and their corresponding registers. 2.2.1 Initialization of the System Control and Status Registers 1, 2 (SCSR1 and SCSR2) When creating the main() function, the first thing to consider is something usually called system initialization. Following a reset, the DSP is unaware (or uses default values) of a set of parameters such as device clocking, phase lock loop (PLL) parameters, Illegal address flag, on-chip DRAM mapping, etc. (don’t bother to remember, the list is fairly long). When programming a PC, these issues have been already solved by the operating system. Once again, in the case of the DSP, it is up to the programmer to deal with these issues and quickly too (early in the main function), otherwise the DSP will most probably lock itself into an endless cycle of resets, or simply perform in unpredictable ways. 14

WRITING AND EXECUTING PROGRAM “HELLO”

The earliest task in program “hello” is to configure the system control and status register 2 (SCSR2). Since this is our first program, we will choose to do a “light” system initialization sequence, just enough to enable the DSP to execute safely the infinite loop that toggles IOPA0 and IOPA1. In the next chapter we will be discussing a rather more extensive system initialization, involving a larger number of configuration registers. Here is the value assignment for the system control and status register 2 (SCRSC2): *SCSR2 = (*SCSR2 | 0x000B) & 0x000F;

Every bit in SCSR2 has its own meaning for the DSP; therefore, it would be useful to examine first what each of these bits stand for.

Figure 2-11. System Control and Status Register 2 bits2.

Bits 0 and 1 indicated as DON and PON define the original mapping of single access RAM (SARAM). Actually these bits can be looked upon as a 2-bit number with the following possible values: 00 : SARAM disabled (not mapped). 01 : SARAM mapped to Program space. 10 : SARAM mapped to Data space. 11 : SARAM mapped to BOTH Program and Data space (default value following a Reset). The “OR” operation of hexadecimal value 0x000B (“1011”) with the content of SCSR2, results in having “1”s written to bit 0 (PON), bit 1 (DON) and bit 3 ( ) of the register. We request SARAM to be mapped to both program and data space (DONPON=11). Henceforth, this should be the case in most programs, unless “strange” application requirements arise.

2

TMS320LF/LC240xA DSP Controllers Reference Guide-System and Peripherals (Texas Instruments)

15

WRITING AND EXECUTING PROGRAM “HELLO”

The latter leaves us with an “ace” on bit. Writing a “1” to this bit disables the “BOOT ROM” option and maps the FLASH ROM to program address space (0x00000x7FFF). Again, this bit should be written as “1”, unless you have boot ROM connected to the DSP, ready to be used; I assume that most probably this is not the case. All in all, set this bit to “1”, unless you have a very-very good reason not to. Writing to the MP/MC bit has no effect (most probably it is “0” since last reset), which explains the “&” (AND) operation. The rest of the bits should be “0” (anyway, 7-15 are reserved). Writing a zero has no effect, except for bit 6 (I/P QUAL), whereas some may be cleared, such as the WD OVERRIDE. The I/P QUAL is related to the analog to digital converter’s (ADC) operation, so let us just keep it “0” and we will come back to it later. As for the WD OVERRIDE, we write a “0” which actually does nothing (as opposed to writing a “1” that clears it). We will soon see why. 2.2.2 The Watchdog DSP controllers, microcontroller’s, PLCs, etc. are destined to eventually reside for a significant amount of time in some dark industrial basement where people don’t tend to visit very often. Considering that programs are man-made and likely to fail due to a wide variety of reasons, certain “fail-safe” mechanisms have been employed. The watchdog is one of them. The Watchdog is an on-chip peripheral, mainly comprised of a counter that requires a periodic reset from the program (an action also referred to as watchdog servicing), before it reaches an upper limit. If the program does not service the watch dog in time, a reset is forced upon the DSP. The watchdog proves to be very useful in cases in which the program “locks” in an endless loop. It can also be a very bad headache when trying to learn how to program a DSP. The watchdog needs to be serviced at any case, unless you disable it. Obviously, this is not the time to build a watchdog service routine, so we better keep it disabled. In order to disable it, we must configure the watchdog control register (WDCR) as follows: *WDCR

= 0x00E8;

Let’s now examine the WatchDog Timer Control Register (WDCR) bit by bit.

16

WRITING AND EXECUTING PROGRAM “HELLO”

Figure 2-12. The Watchdog Control Register (WDCR)3.

The WDPS0-2 bits. Bits 0-2 define the watchdog timer clock prescaling factor. The WD receives clocking from its own clock (derived from the CPU clock signal). The term prescaling actually refers to the division of the frequency of the clocking signal by a factor of /x1, /x2, /x 8, /x16, /x32, /x64, depending on the value written to the WDPS0-2 bits. Here, these bits are all zero (prescaling by /x1 - fastest count-up option), but since we have chosen to disable the WD, the value written has no actual effect. The WDCHK0-WDCHK2 bits. Bits 3-5 are the watchdog check bits. These bits should be written with “101” whenever accessing the WDCR. If any other value is written to these bits, the watch dog will cause a system reset. The check-bits ensure that WDCR will not be accidentally accessed by software. The WDDIS bit. Bit 6 is the watchdog disable bit. In our configuration, this bit is set to “1” to disable the watchdog. Remember that we wrote a “0” to the WD OVERRIDE bit in SCSR2. Had we cleared the WD OVERRIDE (by writing a “1”) in SCSR2, we would not be able to write to WDDIS. Generally, if the WDOVERRIDE bit is cleared (with a “1”-write action), disabling the watchdog will not be possible. ATTENTION! In many cases, the term “clear” may not correspond to a zero-write action. Most register flag-bits are being cleared with a “1”-write action. Obviously, this does not include regular configuration control bits. To conclude, the watchdog may be a bad thing when experimenting with new code, but an absolutely necessary mechanism to reset the controller when operating commercially. For most applications in this text we will be disabling the watchdog in the system initialization sequence.

3

TMS320LF/LC240xA DSP Controllers Reference Guide-System and Peripherals (Texas Instruments)

17

WRITING AND EXECUTING PROGRAM “HELLO”

2.2.3 I/O Pin configuration The 2407A organizes I/O pins in six 8-bit ports; namely, Port A, Port B, Port C, Port D, Port E and Port F. You can physically access the corresponding pins on board header P2, P8 as shown in Figure 2-10. These pins, with a few exceptions, have a dual function (primary/secondary) and should always be configured prior to using them. In program “hello”, we intend to use IOPA0 and IOPA1 and therefore, we need to configure their function to general I/O (secondary). All port bits can be configured by the I/O multiplexing registers A, B and C (MCRA, MCRB, MCRC). As shown in Table 2-1, IOPA0 and IOPA1 are multiplexed with SCITXD and SCIRXD (serial controller interface transmit and receive lines) respectively.

Table 2-1. I/O Multiplexing Register A (MCRA) bits4.

Writing a “0” to any of the bits of MCRA, will configure the corresponding I/O pin to general I/O function. Specifically, in program “hello”, shortly before the main loop: *MCRA &= 0xFFFC;

The “&” (AND) operation writes a “0” to bits 0 and 1, hence configuring the corresponding pins for their secondary (general I/O) function. The rest of the bits in MCRA remain as they were (although we could set them to zero as well). A good strategy is to change only the bits of interest (an even better method is to create C structures for every register used in the program; we will consider it later on). As you can see, there is NO ONE-TO-ONE relation between the I/O multiplexing registers and the ports. MCRA configures Port B as well! Naming conventions in this case are

4

TMS320LF/LC240xA DSP Controllers Reference Guide-System and Peripherals (Texas Instruments)

18

WRITING AND EXECUTING PROGRAM “HELLO”

slightly, but dangerously deceiving. This is a good time to go through these registers in the TMS320LF/LC240xA DSP Controllers Reference Guide - System and Peripherals. Now, since we configured these two pins in port A as general I/O, we need to designate each one of them as being either input or output (direction). In several microcontroller types, there are two registers to determine the direction and the state of an I/O pin. The 2407A uses a 16-bit register for each port, called port data and direction register (i.e., PADATDIR, PBDATDIR, PCDATDIR and PDDATDIR) for both data and direction. The high byte (bits 8-15) configures direction (“0” for input and “1” for output), whereas the low byte (bits 0 -7) contains data.

Figure 2-13. Port A Data and Direction Register (PADATDIR)5.

At this point, let us examine the main loop code of program “hello”. *PADATDIR|=0x0303; // Port A0, A1 bits output and 'high' /* main loop */ while (1) { for(i=0;i EXTPROG PAGE 0 .cinit: > EXTPROG PAGE 0 .const: > B1 PAGE 1 .switch: > EXTPROG PAGE 0 .bss: > EXTDATA PAGE 1 .stack: > DSARAM PAGE 1 .sysmem: > B1 PAGE 1

*/ /* /* /* /* /* /* /*

initialized */ initialized */ initialized */ initialized */ uninitialized */ uninitialized */ uninitialized */

/* Sections declared by the user */ /* vectors: > VECS PAGE 0 */ /* initialized */ }

The text section. Since the text section contains program code, it should obviously be loaded in program memory. In our command file, the text section is assigned to external program SARAM starting at data memory address 08800h. When releasing your DSP application for plant-floor use, the program should reside in flash ROM program memory; downloading the program in flash ROM requires a few more “moves” which will not concern us throughout this book. For the rest of the examples we will be loading the program section (i.e., text) into external SARAM. The cinit section. The cinit section contains initialization code, mainly involving program variables. This section should always be loaded in program memory space. In our command file, the .cinit section is loaded in external SARAM starting at 08800h (same memory locations for program code). Always load the cinit section in the same memory block “hosting” the text section. The const section. This section involves data considered by the C compiler as constants. A standard (but not absolute) policy is to load the constants in program memory. In our command file, the const section is loaded in data memory (B1) starting at address 0300h. The switch section. This section contains jump related data. It should be loaded in program memory, along with the text and init sections; in our case, it is loaded in external single access program RAM, starting at 08800h.

25

WRITING AND EXECUTING PROGRAM “HELLO”

The bss section. The bss section should be loaded in program memory, in the same memory block along with sections cinit and text. In our case, it is stored in external program SARAM, starting at 08800h. The stack section. This is your program stack area. It should obviously reside in data memory. In our command file, the stack section is loaded in a data memory block named DSARAM (internal single access RAM) starting at 0800h. The sysmem section. The heap should obviously reside in data memory. In our command file, it is loaded in block B1 (on-chip dual access memory). The vectors section. This is not a section produced by the C compiler. However, it should always be included in your program. Section vectors contains the CPU core interrupt vectors and should always be loaded in program memory space, starting at address 0000h in the command file (VECS). Although the C compiler does not create this section, it should be part of your program if you want to use interrupts. The interrupt section must be compiled from an .asm file (usually called “cvectors.asm”) and we shall be adding it to project “hello” shortly. Using the above command file is convenient, but not necessarily suitable for every program. Depending on the application, memory allocation strategies may vary; for example, extra stack space may be required, possibly due to recursive algorithms. Also, keep in mind that on-chip memory is much faster than external memory; on the other hand, on-chip memory blocks are significantly smaller than external memory blocks. Throughout most of the examples in this text, the command file introduced above will be used without changes.

26

WRITING AND EXECUTING PROGRAM “HELLO”

27

REFINING PROGRAM “HELLO”

3

Refining program “hello”

3.1 Beginning of execution Let us load our program again to the DSP. Following download, the instruction pointer moves right at the beginning of the cinit section in program memory as shown in the disassembly window in Figure 3-1.

Figure 3-1. Disassembly window after downloading “hello.out”.

Now, let us try something new. Go Debug Reset CPU and check the disassembly window again (Figure 3-2).

Figure 3-2. The Disassembly Window after a CPU Reset.

Something very strange happened! Although your program is still in memory, the DSP chose to ignore the memory location of the cinit section; instead, the instruction pointer now 28

REFINING PROGRAM “HELLO”

moved to address 0x0000 in program memory containing an ADD instruction. Try Debug Go Main now. Oops! No main()? You can obviously tell that the program does not suspend execution at the beginning of the main() function as you may have expected. In fact, the DSP is still running! You can verify this by checking the Debug menu. All execution menu items are inactive, except for Halt. Hit Halt and let us go through what just happened. 3.1.1 The Reset interrupt Vector If no boot ROM is present, then, following a reset, the DSP loads address 0x0000 (program memory) to the instruction pointer. If you take a good look at Figure 2-15, this is the first memory location corresponding to the core interrupt vectors. Address 0x0000 is the location for the reset vector and it should contain a branching instruction (jump) to whatever you want the DSP to do immediately after reset. I think you guessed right. It should jump to the cinit section. Remember the line cased in comments in the SECTIONS clause in “2407_cmd.cmd”? By using the comments, we have excluded the vectors section from being loaded to program memory; hence, when the DSP resets, it looks for a branching instruction at 0x0000, but instead, it finds a random data residue interpreted as an instruction (an ADD in this case); consequently, execution “hangs” since the DSP is executing random instructions in memory. Go File New Source File and create a new file. Save it as “cvectors1.asm”. Do not worry, this is as much as you will be dealing with assembly. Unfortunately, this is the only way to add a section to your C program; so type the following: .ref _c_int0 .sect rset: B

"vectors" _c_int0

;00h reset

Make sure you apply indentation as you see it above; the assembler may have problems if you do not. The “.” prior to the “ref” and “sect” keywords, implies that these strings are assembler directives, not instructions. The .ref directive is used to imply that symbol _c_int0 is externally defined. Actually, this is the symbol created by the C compiler for the cinit section. Keep always in mind that when referencing symbols created in C from an assembly file, the “_” character should be added prior to the symbol. The .sect directive creates a new section. Notice now the first line in section “vectors”. It contains an assembly instruction for an unconditional branch to the _c_int0 code (B _c_int0). That is exactly what we need! So now, after a reset, the DSP will be executing a “jump” to the program’s cinit section and things can start all over again without any unwanted “hangs”.

29

REFINING PROGRAM “HELLO”

Add “cvectors1.asm” file to the project. Then, open the command file and remove the comments from the line loading the vectors section in memory as shown below: vectors:

>

VECS

PAGE 0

/* initialized */

Your project browser window should look like the one shown in Figure 3-3. Save the updated command file and build your project.

Figure 3-3. Project browser window following addition of “cvectors1.asm”.

Load the program into the DSP and do a Go Main. Now, do a Reset CPU and observe what happens. The debugger takes you to file “cvectors1.asm” and suspends execution right before the jump to _c_int0 as shown in Figure 3-4.

Figure 3-4. Execution suspension (break) following a reset.

30

REFINING PROGRAM “HELLO”

If you now do a Go Main, you can rest assured that execution will be suspended right at the beginning of the main() function. Do a few resets just to raise the spirit seeing how nicely it works!

3.2 Completing the “cvectors.asm” file So far, we have ensured that the program will restart “smoothly” following a reset. But this is not the only reason why we need to have a “cvectors.asm” file. In fact, “cvectors.asm” should include the entire interrupt vector table with unconditional branches to all interrupt service routines. 3.2.1 The LF2407A Core Interrupts The 2407A is capable of six (6) maskable interrupts and several software (TRAPS) and non-maskable interrupts (NMI). Practically, your code will be dealing with the six maskable core interrupts (INT1-6), with the extreme exception of the occasional use of software interrupts. The first 6 interrupts (except INT0 which is the reset interrupt vector) correspond to the peripherals of the 2407A through a peripheral interrupt expansion controller (will be discussed later), and it is important that the branching instructions in “cvectors.asm” are pointing to the appropriate interrupt service routines (ISR) implemented in the C code. Depending on the program specifications, you may need to implement less than six ISRs, but it is a good policy to keep a set of 6 routine definitions in your code, so that you will not have to modify the “cvectors.asm” every once in a while. Let us first conclude the “cvectors1.asm” file. Open the editor and type the following: .ref .ref .ref .ref .ref .ref .ref rset: int1: int2: int3: int4: int5: int6: int7: int8: int9: int10: int11: int12:

.sect B B B B B B B B B B B B B

_c_int0 _INT1_GISR _INT2_GISR _INT3_GISR _INT4_GISR _INT5_GISR _INT6_GISR "vectors" _c_int0 _INT1_GISR _INT2_GISR _INT3_GISR _INT4_GISR _INT5_GISR _INT6_GISR int7 int8 int9 int10 int11 int12

;00h ;02h ;04h ;06h ;08h ;0Ah ;0Ch ;0Eh ;10h ;12h ;14h ;16h ;18h

reset INT1 INT2 INT3 INT4 INT5 INT6 reserved INT8 (software) INT9 (software) INT10 (software) INT11 (software) INT12 (software)

31

REFINING PROGRAM “HELLO” int13: int14: int15: int16: int17: int18: int19: int20: int21: int22: int23: int24: int25: int26: int27: int28: int29: int30: int31:

B B B B B B B B B B B B B B B B B B B

int13 int14 int15 int16 int17 int18 int19 int20 int21 int22 int23 int24 int25 int26 int27 int28 int29 int30 int31

;1Ah ;1Ch ;1Eh ;20h ;22h ;24h ;26h ;28h ;2Ah ;2Ch ;2Eh ;30h ;32h ;34h ;36h ;38h ;3Ah ;3Ch ;3Eh

INT13 (software) INT14 (software) INT15 (software) INT16 (software) TRAP NMI reserved INT20 (software) INT21 (software) INT22 (software) INT23 (software) INT24 (software) INT25 (software) INT26 (software) INT27 (software) INT28 (software) INT29 (software) INT30 (software) INT31 (software)

Save the file now as “cvectors.asm”. Notice the first seven .ref directives. They inform the compiler that symbols _c_int0, _INT1_GISR, _INT2_GISR, _INT3_GISR, _INT4_GISR, _INT5_GISR and _INT6_GISR will be externally defined (meaning somewhere in your C code). Keep in mind that if you refer to a symbol defined in C inside an assembly file, you should always add a “_” prefix to the reference (i.e., _c_int0 refers to c_int0, _INT1_GISR refers to INT1_GISR, etc.). As mentioned earlier, the _c_int0 corresponds to a function named “c_int0” automatically generated by the C compiler. The rest are yet to be implemented, but should be defined and declared somewhere in the project code, otherwise the linker will not be able to resolve them and eventually fill your output window with dreadful errors! 3.2.2. Adding Declarations for Core Interrupt Service Routines 1-6 in C Create a new C header file named “DSP24_DefaultISR.h” and type the following: #ifndef DSP24_DEFAULT_ISR_H #define DSP24_DEFAULT_ISR_H interrupt void INT1_GISR(void); interrupt void INT2_GISR(void); interrupt void INT3_GISR(void); interrupt void INT4_GISR(void); interrupt void INT5_GISR(void); interrupt void INT6_GISR(void); #endif

32

REFINING PROGRAM “HELLO”

Save the changes you made. The compiler can resolve the symbols directly from the .c files; therefore, the header file is not really required. However, it is best that you keep it, since it could be used for constant definition and additional include directives. The interrupt keyword informs the compiler that the function that follows is an Interrupt service function; thus, the compiler adds context saving and retrieving specific code (storing/retrieving flags, register values, etc.) at the beginning and the end of the function respectively. The C compiler makes sure that you will not have to worry much about “context saving”, if you only remember to use the interrupt keyword. Notice the names of the routines (INT1_GISR,…,INT6_GISR). These are the symbols referred to in the “cvectors.asm” file that we modified a while ago; now the “_” prefix is omitted since we are typing C code. Don’t worry about the implementation of these functions yet. As long as the linker can locate these symbols referred to in “cvectors.asm”, your project building will be successful. Now, create a new file named “DSP24_DefaultISR.c” for the declaration of the interrupt service routines referred in “cvectors.asm”. Type the following: #include "DSP24_DefaultISR.h" interrupt void INT1_GISR(void) { } interrupt void INT2_GISR(void) { } interrupt void INT3_GISR(void) { } interrupt void INT4_GISR(void) { } interrupt void INT5_GISR(void) { } interrupt void INT6_GISR(void) { }

Although it may seem unimportant for the time being, try to adopt the naming conventions used in this text for your files. For example, “DefaultISR” is a name used commonly in projects for other DSP models (such as TI’s TMS320C2812/F2812™). Most developing principles and methods mentioned in this text may well apply with slight modifications to other DSP products by Texas Instruments. Moreover, it is important that you recognize the role of a file in a project simply by its name. You may find that most programs are portable from one DSP model to another with much less effort than you imagined.

33

REFINING PROGRAM “HELLO”

3.3 Building Register Structure Header files As mentioned in Chapter 2, we may be able to use the union variable type in order to declare structures with fields that correspond to register bits, allowing us to manipulate register values in a more elegant and readable manner in our code. It is time we started building some header files upon this concept; you will realize that there is a huge number of registers to manipulate when using peripherals on the LF2407A. Consider that we had to access two registers just to have two output pins toggling their state. It is advisable to get organized from the beginning, otherwise you may well get acquainted with chaos when dealing with peripherals such as the event managers or the controller area network (CAN) module. 3.3.1 Creating an I/O Register Header File Create a new file named and save it as “DSP24_Gpio.h”. Let us now define all registers used in program “hello” to configure and control I/O pins. Type the following code: #ifndef DSP24_GPIO_H #define DSP24_GPIO_H struct MCRA_BITS { // bits description (MCRA) unsigned int SCITXD_GPIOA0:1; // 0 PORTA0 if 0 unsigned int SCIRXD_GPIOA1:1; // 1 PORTA1 if 0 unsigned int XINT1_GPIOA2:1; // 2 PORTA2 if 0 unsigned int CAP1QEP1_GPIOA3:1; // 3 PORTA3 if 0 unsigned int CAP2QEP2_GPIOA4:1; // 4 PORTA4 if 0 unsigned int CAP3_GPIOA5:1; // 5 PORTA5 if 0 unsigned int PWM1_GPIOA6:1; // 6 PORTA6 if 0 unsigned int PWM2_GPIOA7:1; // 7 PORTA7 if 0 unsigned int PWM3_GPIOB0:1; // 8 PORTB0 if 0 unsigned int PWM4_GPIOB1:1; // 9 PORTB1 if 0 unsigned int PWM5_GPIOB2:1; // 10 PORTB2 if 0 unsigned int PWM6_GPIOB3:1; // 11 PORTB3 if 0 unsigned int T1PWMT1CMP_GPIOB4:1; // 12 PORTB4 if 0 unsigned int T2PWMT2CMP_GPIOB5:1; // 13 PORTB5 if 0 unsigned int TDIRA_GPIOB6:1; // 14 PORTB6 if 0 unsigned int TCLKINA_GPIOB7:1; // 15 PORTB7 if 0 }; union MCRA_REG { unsigned int all; struct MCRA_BITS bit; }; extern volatile union MCRA_REG *MCRAbits; struct PADATDIR_BITS { unsigned int IOPA0:1; unsigned int IOPA1:1; unsigned int IOPA2:1; unsigned int IOPA3:1;

// // // // //

bits description (PADATDIR) 0 A0 data 1 A1 data 2 A2 data 3 A3 data

34

REFINING PROGRAM “HELLO” unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned

int int int int int int int int int int int int

IOPA4:1; IOPA5:1; IOPA6:1; IOPA7:1; A0DIR:1; A1DIR:1; A2DIR:1; A3DIR:1; A4DIR:1; A5DIR:1; A6DIR:1; A7DIR:1;

// // // // // // // // // // // //

4 5 6 7 8 9 10 11 12 13 14 15

A4 data A5 data A6 data A7 data A0 direction (0 input/1 output) A1 direction (0 input/1 output) A2 direction (0 input/1 output) A3 direction (0 input/1 output) A4 direction (0 input/1 output) A5 direction (0 input/1 output) A6 direction (0 input/1 output) A7 direction (0 input/1 output)

}; union PADATDIR_REG { unsigned int all; struct PADATDIR_BITS bit; }; extern volatile union PADATDIR_REG *PADATDIRbits; #endif

Save the file. At this point, it goes without saying that all your code files should be saved inside the current project folder (“hello” in this case). You can obviously deviate from this policy, but this way it is much likely that you will get lost early in your work. File “DSP24_Gpio.h” introduces externally declared structures MCRAbits and PADATDIRbits with bit-fields corresponding to the each bit in registers MCRA and PADATDIR (you may find their descriptions and respective addresses in TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals). Actually, MCRAbits and PADATDIRbits have two fields that share common memory space: The first field is called, all (of type unsigned int) and the second, bit (type MCRA_BITS for MCRA and type PADATDIR_BITS for PADATDIR). Obviously, field all corresponds to a 16-bit integer representation of the register’s content, whereas field bit corresponds to a structure containing separate fields for each individual bit (or groups of bits) in the register. Be very careful when defining these structures! Also, try to follow manual naming conventions for the bit-fields. Notice the comments next to each field; although not obliged to type them, you will not believe how helpful they may turn out to be in the future. Now the only thing missing is a .c file that declares variables MCRAbits and PADATDIRbits. Create a new file and save it as “DSP24_Gpio.c”. Now, type the following: #include "DSP24_Gpio.h" // MCRA declarations volatile union MCRA_REG *MCRAbits=(void*)0x7090; // PADATDIR declarations volatile union PADATDIR_REG *PADATDIRbits=(void*)0x7098;

35

REFINING PROGRAM “HELLO”

This file contains the declarations of all variables declared using the extern keyword in “DSP24_Gpio.h”. The extern keyword is used to avoid declaring the variable inside a header file; declarations should reside inside the corresponding C file, otherwise the linker will probably get “confused” and produce errors related to multiple variable declarations. Notice that variables MCRAbits and PADATDIRbits are pointers, initialized to point at 0x7090 (MCRAbits) and 0x7098 (PADATDIRbits) respectively. If you take a look in the description of these registers in TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals, you will find that MCRA is mapped to address 0x7090 in data memory, whereas PADATDIR is mapped to 0x7098. Save the file. We are almost done transforming program “hello”.

3.4. Completing the System Initialization Sequence It is time to take a closer look at the system initialization sequence and make a few additions. Create a file named “DSP24_Sys.c” and type the following code (try to copy the comments as well); although a bit of a fuss, you will find that this code is used practically unchanged in most examples: #define #define #define #define

WDCR SCSR1 SCSR2 INTMASK

(volatile (volatile (volatile (volatile

unsigned unsigned unsigned unsigned

int int int int

*)0x7029 *)0x7018 *)0x7019 *)0x0004

#define WSGR portFFFF ioport unsigned int portFFFF; void initSystem(void) { *SCSR1 = 0x00FD; /* bit 15 0: bit 14 0: bit 13-12 00: bit 11-9 000: bit 8 0: bit 7 1: bit 6 1: bit 5 1: bit 4 1: bit 3 1: bit 2 1: bit 1 0: bit 0 1: */

reserved CLKOUT = CPUCLK IDLE1 selected for low-power mode PLL x4 mode reserved 1 = enable ADC module clock 1 = enable SCI module clock 1 = enable SPI module clock 1 = enable CAN module clock 1 = enable EVB module clock 1 = enable EVA module clock reserved clear the ILLADR bit

*SCSR2 = (*SCSR2 | 0x000B) & 0x000F; /* bit 15-6 0's: reserved

36

REFINING PROGRAM “HELLO” bit bit bit bit bit */

5 4 3 2 1-0

0: 0: 1: no change 11:

*WDCR = 0x00E8; /* bits 15-8 0's: bit 7 1: bit 6 1: bit 5-3 101: bit 2-0 000: */

do NOT clear the WD OVERRIDE bit XMIF_HI-Z, 0=normal mode, 1=Hi-Z'd disable the boot ROM, enable the FLASH MP/MC* bit reflects state of MP/MC* pin 11 = SARAM mapped to prog and data

reserved clear WD flag disable the dog must be written as 101 WDCLK divider = 1

WSGR = 0x0040; /* bit 15-11 0's: reserved bit 10-9 00: bus visibility off bit 8-6 001: 1 wait-state for I/O space bit 5-3 000: 0 wait-state for data space bit 2-0 000: 0 wait state for program space */ *INTMASK = 0; // masking all core interrupts }

Think of it as an investment for the future. Save the file and let us go though the additions to the system initialization sequence of the original “hello” program. Firstly, we created a separate file just to store system initialization code and it is much better keeping it this way. Additionally, we “cased” the initialization code inside function initSystem(). You will find that the “DSP24_Sys.c” file is used verbatim in most examples throughout in this book (and possibly with/without minor changes, in many programs that you will be writing in the future). Register INTMASK is actually a nickname for the interrupt mask register (IMR). By “zeroing” this register, all core interrupts are masked (i.e., disabled; more about interrupts in the next chapter). Notice that we now added a new pointer constant for system control and status register 1 (SCSR1). Also notice the following define directive and variable definition added at the end of the define directives list: #define WSGR portFFFF ioport unsigned int portFFFF;

As mentioned earlier, registers can be accessed using pointers to designated data memory addresses. Well, this is the case with almost all registers. There are two distinct exceptions: The flash control mode register (FCMR) and the wait state generator register (WSGR), both 37

REFINING PROGRAM “HELLO”

mapped to I/O memory space. In order to access them, we need to define a variable using the ioport keyword and explicitly name them using the address they correspond to (0xFFFF for WSGR and 0xFF0F for FCMR). Do not bother yourself much about it for now; there will be a time for a more careful look upon these registers. In the original program “hello”, we assigned a value to the system control and status register 2 (SCSR2) but did nothing about status control register 1 (SCSR1). Let us go now through the assignments for each bit in the register. The register is loaded with value 0x00FD.

Figure 3-5. Bit positions and names for System Control and Status Register 1 (SCSR1)1.

3.4.1 Configuring the System Control and Status Register 1 The ILLADR bit. A “1” is written to bit 0. This is the illegal address bit (ILLADR) which can be cleared with a “1”-write action. This flag is raised whenever a protected memory location is accessed. It should be cleared upon initialization. The EVA/EVB/CAN/SPI/SCI/ADC CLKEN bits. Bits 2-7 are the clocking enable bits for event manager A (EVA), event manager B (EVB), controller area network (CAN) module, serial peripheral interface (SPI), serial communications interface (SCI) and the analog to digital converter (ADC). In other words, these bits enable/disable clocking signals for all peripherals on the 2407A. Consider the clocking signal as a “life-stream” to these devices; you may as well think of them as inoperative without clocking. Normally you could have them all enabled; it is possible however, to disable clocking for the peripherals not used in the program. In most of the examples in this book they will all be enabled, but this is not a good policy in terms of power consumption. CLKPS0-CLKPS2. Bits 9-11 define the phase lock loop (PLL) module prescaling factor. The PLL receives a signal from an external on-board oscillator and generates an internal clocking signal for the CPU. Using these bits, you may define a multiplication factor for the frequency of the external oscillator. In our case, value “000” corresponds to a x4 multiplication 1

TMS320LF/LC2407A DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

38

REFINING PROGRAM “HELLO”

factor (e.g., if the external oscillator runs at 8MHz, then the CPU will be clocked by a 32 MHz signal coming from the PLL). Although not always the case, this setting tends to shows up more frequently in programs. Normally, a x4 PLL prescaling factor on the 2407A should generate a 40 MHz CPU internal clock signal. You will be able to put this figure to the test during various examples using timers in Chapters 4 and 5. The LPM0, LPM1 bits. Bits 12-13 define the type of low power mode that the CPU will enter, following an IDLE instruction execution. You may read a few more things on IDLE; for now, set these bits to “00” (low power mode IDLE1). The CLKOUT bit. Bit 14 defines the output signal of the CLKOUT pin. If “1”, the CLKOUT pin produces the watchdog clock signal; if “0” the CLKOUT pin produces the CPU clock signal. In our case, we write a “0” (CPU clock output). 3.4.2 Configuring the Wait-State Generator Control Register (WSGR) This register is mapped to I/O memory (address 0xFFFF) and therefore, we are accessing its content as an I/O port and NOT as a pointer to a location in data memory space. A wait-state is an extra CPU cycle delay added to the time required for the CPU to write/read from external memory. In practical terms, the CPU waits for several cycles (configured by WSGR) for the “slow” external memory module to perform a read/write operation.

Figure 3-6. Wait-State Generator Control Register (WSGR)2.

The PSWS0-2 bits. These bits (0-2) define the wait states for read and write actions to/from off-chip program memory. These bits are written with “000” to select zero-wait states (fastest option). Remember that we chose to load the text section to external SARAM, so this setting will affect code execution. The DSWS0-2 bits. Bits 3-5 define the wait states for read and write actions to/from off-chip data memory. These bits are written with “000” for zero-wait states (fastest option). This setting will not affect data access in our program, since we chose the stack and the sysmem section to be loaded in internal SARAM and internal DRAM (B1) respectively. It will

2

TMS320LF/LC2407A DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

39

REFINING PROGRAM “HELLO”

have an effect on the execution of c_int0 though, since the bss section is loaded in off-chip data memory. The ISWS0-2 bits. Bits 6-8 define the wait states for read/write action to/from I/O space. A “001” value (for 1 wait state) is written. You will not be doing much read/write to/from I/O space (except for the WSGR-write action in system initialization). The BVIS0-1 bits. Bits 9-10 define the bus visibility modes regarding internal program and data memory bus activity tracing. What is bus activity tracing? It is a way of tracking the contents of internal program/data bus on the corresponding external program/data bus. A “00” setting corresponds to bus visibility OFF; this means that both internal program and bus do not output their content on the corresponding external program/data bus. This setting favors power consumption. This is a configuration that will hardly change.

3.5 Modifying the main() function We are almost ready. Now the system initialization sequence is a callable function in file “DSP24_Sys.c” and register MCRA and PADATDIR definitions reside in header file “DSP24_Gpio.h”. Let us now introduce a new version of main() which will certainly seem more coherent and structured. Replace the code in “hmain.c” with the following: #include "DSP24_Gpio.h" #include "DSP24_DefaultISR.h" extern void initSystem(void); void main() { int i; initSystem(); MCRAbits->bit.SCITXD_GPIOA0=0; MCRAbits->bit.SCITXD_GPIOA0=0; PADATDIRbits->bit.A0DIR=1; PADATDIRbits->bit.A1DIR=1;

// // // //

A0 A1 A0 A1

is is is is

General I/O General I/O Output output

while (1) { for(i=1;ibit.IOPA0=(PADATDIRbits->bit.IOPA0==1) ? 0 : 1;//Tog. A0 PADATDIRbits->bit.IOPA1=(PADATDIRbits->bit.IOPA1==1) ? 0 : 1;//Tog. A1 } }

Save the changes you made. This looks much better, does it? Notice that we had to declare function initSystem() with the extern keyword, since it resides in a separate C file (you may even skip it, since the compiler will locate the function without it). The most exciting part is 40

REFINING PROGRAM “HELLO”

that we no longer need to figure out hexadecimal numeric values (with a very good chance of being mistaken) for MCRA and PADATDIR. Instead, we may access only the bits we are interested in as fields inside variables MCRAbits and PADATDIRbits. In case you are puzzled by the “->” operator, just remember that MCRAbits and PADATDIRbits are nothing but pointers to memory locations. The C compiler provides a rather more elegant way of accessing fields of structure-type pointers when working with pointers to data structures. Thus, instead of typing, *MCRAbits.bit.SCITXD_GPIOA0=0;

you may type: MCRAbits->bit.SCITXD_GPIOA0=0;

The expressions above are equivalent. It is up to you to choose the approach you are more comfortable with.

3.6 Adding files to the project It is time to put it all together and compile. On the project browser window, remove file (right-click on the file and choose remove) “cvectors1.asm” and add the newly-created “cvectors.asm”. Also, add files “DSP24_Sys.c”, “DSP24_Gpio.c” and “DSP24_DefaultISR.c”. Now, move the mouse pointer over the project name (“hello.pjt”) in the window and rightclick for a pop-up menu. Choose Scan All File Dependencies. If you look very carefully in the tree-view, the Include tree-branch now contains “ÐSP24_Gpio.h” and “DSP24_DefaultISR.h”. The IDE detected all header files included in the code and placed them under the “Include” tree-node. The project browser window should now look like the one shown in Figure 3-7.

Figure 3-7. Project browser window following the addition of new files in “hello.pjt”.

41

REFINING PROGRAM “HELLO”

Notice that we added file “DSP24_Gpio.c”, despite the fact that we used an include directive for the corresponding header file, “DSP24_Gpio.h”. Code Composer Studio™ requires both in the project, so make sure you always add the C file that corresponds to a header you created. Alternatively, you may build C libraries, but this is out of the scope of this text.

3.7 Building and Debugging project “hello” Congratulations! It is time to build once again. Go Project Save and save changes to your project. Now hit rebuild all and enjoy the results! If all went well, connect to the DSP and download the program. 3.7.1 Debugging the Program Let us take a look at the tools provided by the debugger. Choose Go Main. At this point, execution control should have moved right at the beginning of main(). You can now use the usual debugging options: Step Into (F11), Step Over (F10), and Run (F5). Press F11 until you reach the initSystem() function call. Press F11 once more. You will see that a new window opens with the yellow arrow pointing at the beginning of initSystem(). Hitting F11 a few times more, you will see that program execution progresses line-by-line inside the function.

Figure 3-8. Using Step Into (F11) to step through function code.

Now, do a Reset CPU and a Go Main. Again, execution is suspended right at the beginning of main(). Hit F10 several times until you reach the initSystem() function call. Hit F10 42

REFINING PROGRAM “HELLO”

once again. This time, execution suspends exactly at the instruction that follows the initSystem() call in main(). The Step Over (as the name implies) is an execution “jump” to the next instruction inside the current function. 3.7.2 Setting up Breakpoints So far, all debugging options work as in most popular C compilers. Breakpoints are no exception. To setup a breakpoint, just double-click on the gray border area to the left of the line at which you wish to setup the breakpoint. If you double-click again, the breakpoint will be removed. Alternatively, you may right-click on the line and choose Toggle Software Breakpoint. You will probably see a few more options, but there’s no rush to use them all.

Figure 3-9. Toggling Breakpoints using the right-click popup menu.

Do a reset and hit F5 to run the program immediately. If all went well, you should see an execution suspension occurring right on the line that you placed your breakpoint. 3.7.3 Using the Watch Window To create a watch, just go to View Watch Window. You should now be able to see the watch window either docked somewhere on the main form, or floating amongst other windows. 43

REFINING PROGRAM “HELLO”

Figure 3-10. The Watch Window.

To add a watch, just click on the Watch 1 tab and double click on the first cell under the Name column in the grid. Just type your variable name and watch the results. You can always use the Radix column options to display your variable’s content in decimal, hexadecimal or even binary representations.

Figure 3-11. Changing the radix display setting.

3.8. Summary At this point, we may summarize the basic building blocks of a program for the 2407A. Henceforth, both the linker command file (“2407_cmd.cmd”) and the assembler file containing interrupt branches (“cvectors.asm”) will be used verbatim in most applications. The same stands for the system initialization function in “DSP24_Sys.c”. Additionally, header files containing register structures will be gradually appended while developing new programs. Always save your projects separately and try to follow a header file oriented organization.

44

REFINING PROGRAM “HELLO”

45

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

4

Using Interrupts and the General Purpose Timers

4.1 A brief introduction to Interrupts of the 2407A For those of you acquainted with event driven programming in Unix or other operating systems, you surely realize the importance of handling asynchronous events in a program. Think of interrupts as special events, normally triggered by external sources involving peripherals. Depending on the case, you may choose to ignore these signals; however, in other cases, you may choose to “interrupt” the sequential flow of execution and branch code execution to a special function that handles the event that triggered the interrupt. The special routines that handle interrupt signals are usually called interrupt handlers, but you will find the term interrupt service routines (ISR) rather more commonly used. The 2407A is able to “sense” numerous interrupt sources, mainly related to its peripherals. 4.1.1 The Peripheral Interrupt Expansion Controller (PIE) The 2407A acknowledges interrupts in two levels. The core itself provides six maskable interrupts (INT1-6). Technically, each of those interrupts may correspond to one specific source. When programming interrupts for a PC, we know that there is a one-to-one mapping from a peripheral interrupt source to a core interrupt in the CPU (i.e., the Intel x86 has 256 core interrupt vector table entries). Unfortunately, we do not have the luxury of using a powerful Pentium™ on our DSP board. On the other hand, the DSP should be able to serve up to 49 interrupt sources, most of which correspond to its peripherals. To overcome the problem of having a great number of hardware interrupts (as opposed to the six available maskable core interrupts) to be served by the CPU, these interrupts are organized in groups or levels, each one corresponding to one of the six core maskable interrupts (INT1-6). This is actually where the peripheral interrupt expansion controller (PIE) kicks-in. The PIE “intercepts” interrupt signals from the various peripherals and consequently triggers the appropriate core interrupt. It is as simple as that: Every maskable core interrupt in the 2407A, is “responsible” for several interrupt sources. How is this happening? Better see this through an example and fill in the details as we go.

46

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

Figure 4-1. The Peripheral Interrupt Expansion scheme of the 2407A1.

4.2 Using a General Purpose Timer The LF2407A is equipped with four general purpose timers (timer 1-4), all identical in every aspect. For those already acquainted with embedded programming, it is common knowledge that if you want your program to “do business”, you must always have at least one

1

TMS320LF/LC2407A DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

47

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

timing source to synchronize events. “Time awareness” and “periodic execution” are important factors to consider when controlling a motor, polling sensors or filtering signals. 4.2.1 Project “Timer1” Create a new project folder named “Timer1”. Copy files “cvectors.asm”, “2407_cmd.cmd”, “DSP24_Sys.c”, “DSP24_DefaultISR.h” and “DSP24_DefaultISR.c” to the new folder and create a new project named “Timer1”. Now, create a new file named “T1main.c” and type-in the following code: extern void initSystem(void); extern void initTimer1(void); void main() {

initSystem(); initTimer1(); asm(" CLRC INTM"); // Enable Global Interrupts while (1) { } }

This is an even smaller main() function than the one in program “hello”! Notice externally implemented function initTimer1() called right after system initialization. This function will start the timer and enable the desired interrupts. Notice the inline assembly instruction asm(“ CLRC INTM”). If you are using interrupts in your program, you must use this instruction to enable them. By default, following a reset, interrupts are disabled. The DSP has a global interrupt switch (INTM) that disables/enables interrupts. You will realize that it comes very handy in many occasions in our programs. You may disable interrupts using inline assembly instruction asm(“ SETC INTM”). 4.2.2 Timer 1 initialization In order to work with a GP Timer, much like with any other device on the 2407A, we will have to access the relative configuration and control registers. Think of a timer, roughly as a counting register (CNT) and a period register (PR); the counting register counts up/down (or both) until it reaches the value loaded in the period register. The timer is able to generate interrupt signals for several events related to the counter value. We will go through those events and how to use them to achieve our goals. To get us started with the timer, let us first create structures for the appropriate registers. Create a file named “DSP24_Ev.h” and type the following: 48

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS #ifndef DSP24_EV_H #define DSP24_EV_H //Timer 1 CONtrol register (T1CON) struct T1CON_BITS { unsigned int rsvd1:1; // 0 Reserved unsigned int TCMPREN:1; // 1 Timer Compare Enabled unsigned int TCLD:2; // 2-3 Compare register reload condition unsigned int TCLKS:2; // 4-5 Clock Source Select unsigned int TENABLE:1; // 6 Timer Enable unsigned int rsvd2:1; // 7 Reserved unsigned int TPS:3; // 8-10 Input Clock Prescaling unsigned int TMODE:2; // 11-12 Count mode selection unsigned int rsvd3:1; // 13 reserved unsigned int SOFT:1; // 14 EMUlation control bit 0 (soft) unsigned int FREE:1; // 15 EMUlation control bit 1 (free) }; union T1CON_REG { unsigned int all; struct T1CON_BITS bit; }; extern volatile union T1CON_REG *T1CONbits; //Event Manager A Interrupt Mask Register (EVAIMRA) struct EVAIMRA_BITS { unsigned int PDPINTA:1; // 0 Enable PDPINTA unsigned int CMP1INT:1; // 1 Enable unsigned int CMP2INT:1; // 2 Enable unsigned int CMP3INT:1; // 3 Enable unsigned int rsvd1:3; // 4-6 reserved unsigned int T1PINT:1; // 7 Enable unsigned int T1CINT:1; // 8 Enable unsigned int T1UFINT:1; // 9 Enable unsigned int T1OFINT:1; // 10 Enable unsigned int rsvd2:5; // 11-15 reserved }; union EVAIMRA_REG { unsigned int all; struct EVAIMRA_BITS bit; }; extern volatile union EVAIMRA_REG *EVAIMRAbits; //Event Manager A Interrupt Flag Register A (EVAIFRA) struct EVAIFRA_BITS { unsigned int PDPINTA:1; // 0 Flag PDPINTA unsigned int CMP1INT:1; // 1 Flag unsigned int CMP2INT:1; // 2 Flag unsigned int CMP3INT:1; // 3 Flag unsigned int rsvd1:3; // 4-6 reserved unsigned int T1PINT:1; // 7 Flag unsigned int T1CINT:1; // 8 Flag unsigned int T1UFINT:1; // 9 Flag unsigned int T1OFINT:1; // 10 Flag

49

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS unsigned int

rsvd2:5;

// 11-15

reserved

}; union EVAIFRA_REG { unsigned int all; struct EVAIFRA_BITS bit; }; extern volatile union EVAIFRA_REG *EVAIFRAbits; extern volatile unsigned int *T1CNT; /* GP timer 1 counter reg */ extern volatile unsigned int *T1CMPR; /* GP timer 1 compare reg */ extern volatile unsigned int *T1PR; /* GP timer 1 period reg */ #endif

Save your file and create the corresponding .c file, “DSP24_Ev.c” for the declarations. Type: #include "DSP24_Ev.h" volatile unsigned int *T1CNT=(void*)0x7401; /* GP timer 1 counter reg */ volatile unsigned int *T1CMPR=(void*)0x7402; /* GP timer 1 compare reg */ volatile unsigned int *T1PR=(void*)0x7403; /* GP timer 1 period reg */ //T1CON volatile union T1CON_REG *T1CONbits=(void*)0x7404; // EVAIMRA volatile union EVAIMRA_REG *EVAIMRAbits=(void*)0x742C; // EVAIFRA volatile union EVAIFRA_REG *EVAIFRAbits=(void*)0x742F;

Save the file and let us take a look at the registers declared in “DSP24_Ev.c” so far. Every GP Timer is configured with its own timer control register (TxCON, where x=1,2,3,4). Upon initialization, you will have to load a value to the period register (T1PR), clear the count register (T1CNT) and load the compare register (T1CINT) with a comparison value (less than the value loaded in PR), if interested in comparison except for period and overflow/underflow related events. All in all, GP Timers are able to produce four types of interrupts: Timer overFlow (TxOFINT), timer underFlow (TxUFINT), timer compare (TxCINT) and timer period match (TxPINT). You can write an interrupt service routine for each of those events to facilitate the needs of your program. Except for the T1CONbits structure, we also defined structures EVAIMRAbits and EVAIFRAbits for registers EVAIMRA and EVAIFRA in the “DSP24_Ev.h” file. Be aware that almost every peripheral in the 2407A comes with its own interrupt mask and flag Register. In the case of timer 1, the interrupt mask bits are located in register EVAIMRA. Accordingly, the interrupt flag bits can be found in register EVAIFRA. If any of the timer 1 interrupts are to be used, you must set the appropriate mask bits in EVAIMRA (write a “1”), an action referred to as “interrupt unmasking”. Additionally, following the execution of an interrupt service routine, the corresponding interrupt flag bit should be MANUALLY (this means it is 50

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

your responsibility) cleared in EVAIFRA (by writing a “1”). If you are already feeling desperately confused, do not panic and take a deep breath! Examples are on the way! Just make sure you remember the concepts. Although you should not bother your head too much for now, keep in mind that timers play important role in generating pulse width modulated (PWM) signals, involving more control registers to configure actions related to the TxOF, TxUF, TxCMP and TxPINT events. For this reason, they are considered units of greater modules called, event managers. In fact, timers 1 and 2 belong to event manager A, whereas timers 3 and 4 belong to event manager B. For now, let us focus on writing code that initializes GP Timer 1 and performs a specific action upon each of the four related interrupts. Create a new file called “DSP24_Timer1.c” and type the following: #include "DSP24_Ev.h" #include "DSP24_Core.h" void initTimer1(void) { *T1CNT=0; *T1PR=30000; *T1CMPR=15000;

// Set Timer1 Counter to 0 // Set Timer1 Period to 30000 // Set Timer1 Compare to 15000

T1CONbits->bit.rsvd1=0; T1CONbits->bit.TCMPREN=1; T1CONbits->bit.TCLD=00; T1CONbits->bit.TMODE=01; T1CONbits->bit.TPS=000; T1CONbits->bit.rsvd2=0; T1CONbits->bit.rsvd3=0; T1CONbits->bit.TCLKS=00; T1CONbits->bit.SOFT=0; T1CONbits->bit.FREE=0;

// // // // //

reserved Enable Timer Compare Reload compare reg on underflow Continuous Up/Down mode Input Clock PreScale /x1

// Internal Clock source select // immediate stop on emulation suspend

EVAIFRAbits->bit.T1PINT=1; // EVAIFRAbits->bit.T1CINT=1; // EVAIFRAbits->bit.T1UFINT=1;// EVAIFRAbits->bit.T1OFINT=1;//

clear clear clear clear

EVAIMRAbits->bit.T1PINT=1; // EVAIMRAbits->bit.T1CINT=1; // EVAIMRAbits->bit.T1UFINT=1;// EVAIMRAbits->bit.T1OFINT=1;//

enable enable enable enable

Timer1 Timer1 Timer1 Timer1 Timer1 Timer1 Timer1 Timer1

Period interrupt flag Compare interrupt flag UnderFlow interrupt flag OverFlow interrupt flag Period interrupt Compare interrupt UnderFlow interrupt OverFlow interrupt

*IMR|=0x0002;

// enable core level interrupt 2

T1CONbits->bit.TENABLE=1;

// enable the timer

}

51

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

As you can see, we are using six distinct registers to initialize the timer and the relative interrupts, T1CNT, T1PR, T1CMPR, T1CON, EVAIFRA and EVAIMRA. Initially, we set the timer counter (T1CNT) to zero and set a period (T1PR) equal to 30000, whereas the compare register (T1CMPR) is loaded with 15000, which is half the value in the period register. The value loaded in T1PR, considering the prescaling of the timer’s clocking signal, will define the period of a T1PINT (period match) interrupt. Obviously, a compare interrupt will occur between two consecutive period interrupts. As a friendly suggestion, keep your oscilloscope up and ready. In many cases, it is very important to be aware of your timer’s actual frequency and therefore, a good idea would be to use I/O pins toggling upon certain events (e.g., an underflow) so that you may measure time intervals with your scope (there’s always many things that can go wrong when calculating the period in time units; always verify your calculations with a scope!). Now, let’s go through the bitwise setup of timer 1 control register (T1CON). Keep in mind that all four timer control registers (T1CON, T2CON, T3CON and T4CON) are identical, but you will find that certain bits have a different configuration effect on some, whereas on others, they are just reserved or ineffective.

Figure 4-2. The General Purpose Timer Control Register (TxCON, x=1,2,3,4) 2.

The SELT1PR/SELT3PR bit. Bit 0 is reserved for T1CON (timer 1). However, for T2CON and T4CON (timer 2 and timer 4), it configures the period register to be used. Timer 2 and timer 4 can be configured to use the period registers of timer 1 and timer 3 respectively. The TECMPR bit. Bit 1 is the timer compare enable bit. If “1”, compare operation is enabled; if “0”, compare operation is disabled. The TCLD0-1 bits. Bits 2-3 define the compare register reload condition. You may change the value of the compare register at any time during timer operation. The value is 2

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

52

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

initially stored in a shadow register (think of it as a temporary storage for T1CPMR future content) until an underflow or period match event occurs. If TCD0TCLD1=00, T1CPMR is reloaded with the content of the shadow register on the next underflow; if TCD0TCLD1=01, T1CMPR is reloaded with the content of the shadow register on the next period match. In function initTimer1(), we specify a reload on underflow (“00”). The TCLKS0-1 bits. Bits 4-5 define the clock source for the timer. Usually, as in our case, the internal clock is used (i.e., TCLKS are “00”), but you may also use external clocking, or even clocking from the quadrature encoder pulse circuit (QEP). Unless you have a very specific need in your program, try to use the internal clock for the timer. The T2SWT1/T4SWT3 bit. Bit 7 is the start with timer 1/timer 2 bit. It is reserved in T1CON (and T3CON). If this bit is “1” in T2CON or T4CON, then the timer will be triggered by timer 1 or timer 3. This is simply an option to provide the ability to start two timers at exactly the same time. The TPS0-2 bits. Bits 8-10 specify the counter prescaling factor. In our case, we use “000”, corresponding to /x1 prescaling (“001” is for /x2, “010” is for /4, etc.). The TMODE0-1 bits. Bits 11-12 configure the counting mode of the timer. GP Timer can be set to count up and up-down, continuously or directionally. In this example, we choose continuous up/down mode (“01”). The rest of the settings include: “00” for stop/hold, “10” for continuous up and “11” for directional up/down. The FREE-SOFT bits. Bits 14 and 15 define the behavior of the timer upon emulation suspension events (breakpoints). When debugging, you want the timer to be fully stopped upon a breakpoint, so keep both bits to zero, unless you have a strong reason not to. The TENABLE bit. Bit 6 starts the timer. You should first setup the rest of the bits before writing a “1” to this bit. If you intend to use timer 1 interrupts (as in our case), also setup the relative interrupt flags and masks before you enable the timer. Okay, we are done with timer configuration; but we have not done anything yet regarding the related interrupts. In order to enable some/all of the interrupts related to timer 1, we have to access register EVAIMRA. This is the event manager A interrupt mask register. Remember that timer1 is part of a greater unit called event manager A (EVA). Register EVAIMRA contains interrupt mask bits for a set of modules contained in event manager A (compare unit, timers 1 and 2, capture unit, dead-band unit). Actually, if you take a look in Chapter 6 of the TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals, you will find that there are three interrupt mask registers for EVA (EVAIMRA, EVAIMRB and EVAIMRC) just to accommodate the numerous interrupt sources originating in 53

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

the event manager. Since the timer 1 interrupt mask resides in EVAIMRA, we will focus on this particular register for now.

Figure 4-3. Register EVAIMRA3.

Looking at the bits of EVAIMRA in Figure 4-3, you may recognize the interrupt enable bits for timer 1 events (bits 7-10), but you will also see enable bits for several other interrupts (PDPINTA , CMP1INT, CMP2INT, CMP3INT). They belong to other very useful modules in EVA. For now, focus on timer 1, but keep in mind that your will be seeing them very soon. For every interrupt mask register in EVA, there is an identically mapped interrupt flag register (IFRA). Consequently, for EVAIMRA we get an EVAIFRA (EVAIFRB for EVAIMRB, etc.). Interrupt flag registers are important since it is up to the programmer’s provision to clear an interrupt flag upon service. If you don’t clear an interrupt flag, this will result in having an interrupt service request pending forever. In other words, your ISR will be executed only once or maybe even never. It is imperative to clear the interrupt flag of a peripheral somewhere in the corresponding ISR, preferably at the end of the routine; it is also wise to clear the interrupt flags upon initialization of the peripheral. Clearing a flag occurs by writing a “1”, in the corresponding interrupt flag register. Now notice the line just before enabling the timer in initTimer1(). *IMR|=2;

// enable core level interrupt 2

As mentioned earlier, all timer 1 interrupts will be serviced, at first, by an INT2 core interrupt. Thus, it is imperative to enable this interrupt in the core, using the core interrupt mask register (IMR). Bits 0-5 correspond to interrupt masks for INT1-6. Although you have enabled the timer 1 interrupts in EVAIMRA, if INT2 is not enabled in IMR (by writing a “1” in bit 1), nothing will ever happen. And yes, you guessed right! For core IMR we have a

3

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

54

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

corresponding core interrupt flag register (IFR), containing the interrupt flags for INT1-6. And yes, you also have to manually clear these flags by writing a “1”. 4.2.3 Timer 1 Interrupt Service Routines It is time to go through the strange stuff now. As mentioned earlier, peripheral interrupts are grouped in a way such as to have them served by only 6 core interrupts. Take a look at Chapter 2 in the TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals. You will find a long table with all interrupt sources, groups/levels and corresponding vectors. Table 4-1 shows a portion of this table regarding timer 1 events.

Table 4-1. CPU interrupt vector and Peripheral interrupt Vector for Timer 1 events4.

In Table 4-1, notice that all timer 1 interrupts correspond to CPU interrupt vector INT2. Practically, this means that whenever any of those interrupts is triggered, the peripheral interrupt expansion controller (PIE) will trigger an INT2 core interrupt. You should not worry about how this happens; you should instead be very worried about how to handle it. Upon a timer 1 related interrupt, assuming that core interrupt INT2 is enabled, the CPU will attempt to “jump” to the INT2 core interrupt service routine. The code inside this routine should then be able to “find out” WHO caused the interrupt; obviously, it can be only one source amongst many at a time. The only way to know is by looking at the peripheral interrupt vector (column 4 in Table 4-1). The peripheral interrupt vector is a number loaded in the peripheral interrupt vector register (PIVR). This vector is unique for each peripheral interrupt source inside the same interrupt level (i.e., INT2). After the interrupt source has been 4

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

55

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

identified, the code should “jump” to a subsidiary interrupt service routine (also referred to as specific interrupt service routine-SISR) to handle the interrupt. To resume, interrupt service occurs in two stages: At first, execution branches to the appropriate core interrupt service routine (also referred to as general interrupt service routine-GISR), which, in the case of timer 1, is INT2 GISR. The GISR then, “looks” inside the PIVR and according to the content, branches again to the specific interrupt service routine (SISR) for this particular interrupt source. Okay, I know it sounds frustrating, but nothing that a good example cannot put in order! So, start typing! Remember file “DSP24_DefaultISR.h”? Open it and add the following lines: // Timer 1 Period Match interrupt interrupt void T1PINT_ISR(void); // Timer 1 Compare interrupt interrupt void T1CINT_ISR(void); // Timer 1 Underflow interrupt interrupt void T1UFINT_ISR(void); // Timer 1 Overflow interrupt interrupt void T1OFINT_ISR(void);

The overall code in “DSP24_DefaultISR.h” should now look like: #ifndef DSP24_DEFAULT_ISR_H #define DSP24_DEFAULT_ISR_H New declarations

// Timer 1 Period Match interrupt interrupt void T1PINT_ISR(void); // Timer 1 Compare interrupt interrupt void T1CINT_ISR(void); // Timer 1 Underflow interrupt interrupt void T1UFINT_ISR(void); // Timer 1 Overflow interrupt interrupt void T1OFINT_ISR(void); interrupt void INT1_GISR(void); interrupt void INT2_GISR(void); interrupt void INT3_GISR(void); interrupt void INT4_GISR(void); interrupt void INT5_GISR(void); interrupt void INT6_GISR(void); #endif

56

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

Save the changes. Notice that we added four interrupt function declarations: T1PINT_ISR(), T1CINT_ISR(), T1UFINT_ISR() and T1OFINT_ISR(). These functions will be the specific interrupt service routines (SISRs) for the four timer1 interrupts. Before implementing some of the functions contained in “DSP24_DefaultISR.h”, we will need to define a few more register variables for the CPU core and the PIE controller. Create a new header file “DSP24_Core.h” and add the following: #ifndef DSP24_CORE_H #define DSP24_CORE_H extern volatile unsigned int *IFR; extern volatile unsigned int *IMR; extern volatile unsigned int *PIVR; #endif

Keep in mind that PIVR is a PIE register, not a core register, but it will be rather more applicable to include it with the core register definitions (you may find that in the ticds library, PIE registers are defined in a separate header file). Create the corresponding “DSP24_Core.c” file with the declarations: #include "DSP24_Core.h" volatile unsigned int *IFR=(void*)0x0006; volatile unsigned int *IMR=(void*)0x0004; volatile unsigned int *PIVR=(void*)0x701E;

Save the changes. Register IFR contains core interrupt flags. Accordingly, IMR contains the core interrupt mask bits. Register PIVR returns the peripheral interrupt vector, a number very useful in identifying the source of the interrupt. Now, open file “DSP24_DefaultISR.h” to include the header “DSP24_Ev.h” and “DSP24_Core.h”. #ifndef DSP24_DEFAULT_ISR_H #define DSP24_DEFAULT_ISR_H Additional include directives

#include "DSP24_Ev.h" #include "DSP24_Core.h"

If you are finished, return to “DSP24_DefaultISR.c” and add the following code implementing T1PINT_ISR(), T1CINT_ISR(), T1UFINT_ISR() and T1OF_ISR(): 57

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

// Timer 1 Period interrupt interrupt void T1PINT_ISR(void) { EVAIFRAbits->bit.T1PINT = 1; // Clear Period Interrupt flag asm(" NOP"); } // Timer 1 Compare interrupt interrupt void T1CINT_ISR(void) { EVAIFRAbits->bit.T1CINT = 1; // Clear Compare Interrupt flag asm(" NOP"); } // Timer 1 Underflow interrupt interrupt void T1UFINT_ISR(void) { EVAIFRAbits->bit.T1UFINT = 1; // Clear UnderFlow Interrupt Flag asm(" NOP"); } // Timer 1 Overflow interrupt interrupt void T1OFINT_ISR(void) { EVAIFRAbits->bit.T1OFINT = 1; // Clear OverFlow Interrupt Flag asm(" NOP"); }

In function INT2_GISR(), add the code shown below. // General Iinterrupt Service Routine for all INT2 level peripherals interrupt void INT2_GISR(void) { unsigned int peripheral; asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM"); switch (peripheral) { case 0x0027: // Timer 1 period T1PINT_ISR(); break; case 0x0028: // Timer 1 Compare T1CINT_ISR(); break; case 0x0029: // Timer 1 UnderFlow T1UFINT_ISR(); break; case 0x002A: // Timer OverFlow T1OFINT_ISR(); break; default: break; } // clear the core INT2 flag by writing a "1" in bit 1 *IFR|=2; }

58

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

Let us now go through function INT2_GISR(). This is the general interrupt service routine, called whenever a timer 1 interrupt has occurred. As mentioned earlier, amongst other things needed to be “worked out” inside an ISR, is to clear the flag of the relative interrupt. Since INT2 is a core interrupt, we need to write a “1” to bit 1 of the core interrupt mask register (IMR); hence, the instruction: *IFR|=2;

Here comes the strangest part of it all now! As mentioned earlier, we must “pin-point” the source of the interrupt. In order to do that, we store the value of the peripheral interrupt vector register (PIVR) into an integer local variable called peripheral: asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM");

Notice the assembly command that disables the interrupts (“ SETC INTM”) before copying the value and the assembly command that enables them (“ CLRC INTM”) after the value assignment. Usually this is done as a safety precaution against the “firing” of another interrupt while trying to retrieve the value of PIVR. If such a thing happens, the value of PIVR may change before we have the time to copy it into our variable, thus “misleading” the code to execute something else (or nothing) instead of the SSIR corresponding to the original interrupt source. After the retrieval of the peripheral interrupt vector, interrupt operation is reestablished, as well as the possibility of having the current ISR execution “disrupted” by another interrupt (it is a possibility, if many peripherals are enabled). Now that we have the peripheral interrupt vector stored away in a local variable, we may examine who caused the interrupt. If you look at section 2.3 in TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals, you will find an extensive table of 49 interrupt sources and their corresponding peripheral interrupt vectors. As mentioned earlier, each PIV is unique within the same core level. Hence, we may use a simple switch statement to branch to the appropriate ISR. Although an interrupt vector should be an explicit address to the memory location of an ISR, here we use the PIVR contents merely as an identifier rather than a memory address to branch to. You will find that this approach is rather more simple and effective as any other. Your goal is to have all interrupt service routines saved in a “DSP24_DefaultISR.c” file and simply add code to whichever SISR you wish. Under the current approach, you may keep the switch statements within each GISR with all your applications and change only the SISR code.

59

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

4.3 Executing the Program If you haven’t already added all the files to your project, go to the project browser window and add “T1main.c”, “Timer1.c”, “DSP24_DefaultISR.c”, “DSP24_Ev.c”, “DSP24_Core.c”, “cvectors.asm” and “2407_cmd.cmd” from folder “Timer1”. Don’t forget to add the library file “rts2xx.lib”. The project browser window should now look like the one in Figure 4-3.

Figure 4-3. Project browser window for “Timer1.pjt”.

Do a Rebuild All and load the program to the DSP. Before execution, do a CPU Reset. Now, let us place a few breakpoints just to make sure our code is doing what it’s supposed to. Go to “DSP24_DefaultISR.c” and add breakpoints on the asm(“ NOP”) statements (they are simply a No OPeration instruction serving as a delay) inside the T1PINT_ISR(), T1CINT_ISR(), T1UF_ISR() and T1OF_ISR() functions, as show in Figure 4-4.

60

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

Figure 4-4. Breakpoints inside the four timer 1 SISRs.

Now, do a Run or press F5. If all went well, execution should suspend right on the breakpoint we setup inside T1CINT_ISR(). It makes sense that T1CNT, while counting up towards T1PR (30000), will “meet” the value of T1CMPR (15000), hence the interrupt. Logically, a period match interrupt should be triggered. Press F5 again to verify the anticipated result. If all went nice, once again execution should be suspended inside T1PINT_ISR(). Let us think a bit. The timer is setup to do a continuous up/down run. It makes sense that the counter should start counting backward, shortly after reaching the period value. The question is: Will it overflow? Press F5 to find out. I am sure you will see a suspension on the breakpoint inside the T1UF_ISR() (underflow), meaning the counter did not overflow. Play a little bit with the F5 key. I am sure you will notice that the timer never overflows. That’s not a bad thing necessarily. In fact, in order for the timer to overflow, the counter must reach 0xFFFF (obviously, we are a bit far from that in our case). The events that will be of the outmost interest are the period match, compare and underFlow in most applications.

4.4 Summary Keep the “DSP24_DefaultISR.c” and “DSP24_DefaultISR.h” files and gradually expand them until you have included ISRs for all known interrupt sources. In fact, file “DSP24_DefaultISR.c” is nothing but a template to conveniently use in all of your projects henceforth. In the following chapters, we will be adding some more code in the general interrupt service routines by including switch statements for numerous peripheral interrupt vectors at each core level (INT1-6). 61

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

Having worked with a GP Timer, we may now proceed into exploring the event managers a little bit more extensively. So, be prepared to expand file “DSP24_Ev.h” with a lot of structures corresponding to event manager A and B configuration and control registers.

62

USING INTERRUPTS AND THE GENERAL PURPOSE TIMERS

63

THE EVENT MANAGERS

5

The Event Managers

5.1 Overview on the Event Managers As mentioned earlier, the 2407A is equipped with event managers A and B (EVA and EVB). EVA and EVB are identical in every aspect; for simplicity reasons, we will be going through the characteristics of EVA. EVA includes two general purpose timers (timer 1 and timer 2), three compare units, three capture units and one quadrature encoder pulse (QEP) circuit.

Figure 5-1. Block diagram of Event Manager A1.

1

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

64

THE EVENT MANAGERS

The general purpose timers. We have already made our first acquaintance with the general purpose timers. So far, we have only used them to produce periodic events (period match, compare and underflow interrupts). In the context of the event manager, timers may be additionally configured to produce pulse width modulated signals on pins TxCMP/TxPWM (x=1,2,3,4). Throughout the present chapter, we will be using timer 1 to produce a PWM signal. Moreover, you should think of timers as the “life force” of the event managers, providing clocking to other units, such as the compare units (Figure 5-1). The compare units. The three compare units (per event manager) can be specifically used for PWM signal production on pins PWM1, PWM2, PWM3, PWM4, PWM5 and PWM6 in the case of EVA and PWM7, PWM8, PWM9, PWM10, PWM11, PWM12 for EVB. They are clocked by timer 1 (EVA) and timer 3 (EVB) and they can be configured to produce pulses in pairs of pins (e.g., PWM1 and PWM2). The compare units can use a special dead-band unit as an accessory to produce artificial “dead-bands” for “active high”-“active low” PWM pin pairs. The capture units. Capture units may come-in very handy when trying to “capture” pulses of external origin. Such an example could be the bounce-back pulse from a sonar transducer, or a series of pulses from a rotary motor encoder. In fact, this is probably what the designers of the event managers had in mind when they were designing capture units. Actually, to accommodate our needs in terms of capturing motor encoder pulses, we may configure the quadrature encoder pulse circuit for quadrature motor encoder pulse capturing to determine both rotation speed and direction. Concluding, event managers comprise several interconnected units to accommodate the numerous specific needs that may arise when controlling a motor and/or when receiving feedback in the form of pulses. EVA and EVB are highly configurable to the last detail; therefore, they involve a large set of configuration and control registers. Along with the CAN controller, they are the most complicated peripherals on the 2407A. The large number of registers to deal with may give you a headache, but it is something that can be dealt with, mainly by properly organizing your header files and functions.

5.1 Using Timer 1 for Pulse Width Modulation Let us create a new project folder named “t1pwm”. Copy all source files from “Timer1” folder into the new one, including the linker command file “2407_cmd.cmd” and assembly file “cvectors.asm”. Now, go to folder “hello” and copy files “DSP24_Gpio.h” and “DSP24_Gpio.c” to your new folder (since we will be using an I/O pin, we should make sure that this particular pin is configured for PWM operation with the appropriate I/O mux register defined in “DSP24_Gpio.h”). Now, create a new project “t1pwm”. Add all files to your new project, and don’t forget to add “rts2xx.lib” after you finished. 65

THE EVENT MANAGERS

5.1.1 Expanding “DSP24_Ev.h” Open “DSP24_Ev.h”. In order to use timer 1 to produce a PWM signal, we will have to configure additional registers. Add the following structure definitions: // General Purpose Timer CONtrol register A (GPTCONA) struct GPTCONA_BITS { unsigned int T1PIN:2; // 0-1 Polarity of GP timer 1 compare unsigned int T2PIN:2; // 2-3 Polarity of GP timer 2 compare unsigned int rsvd1:2; // 4-5 Reserved unsigned int TCOMPOE:1; // 6 CoMPare Output Enable unsigned int T1TOADC:2; // 7-8 Start ADC with Timer 3 unsigned int T2TOADC:2; // 9-10 Start ADC with Timer 4 unsigned int rsvd2:2; // 11-12 Reserved unsigned int T1STAT:1; // 13 Timer 3 STATus unsigned int T2STAT:1; // 14 Timer 3 STATus unsigned int rsvd3:1; // 15 reserved }; union GPTCONA_REG { unsigned int all; struct GPTCONA_BITS bit; }; extern volatile union GPTCONA_REG *GPTCONAbits;

The event managers offer the “luxury” of configuring certain actions upon timer-related events. Actually, we may use a GP Timer to produce PWM on the T1PWM/T1CMP pin, or even start the analog to digital converter (ADC). Configuration of these actions (and several others) can be done through the appropriate general purpose timer control register (i.e., GPTCONA). Obviously, there are two general purpose timer control registers, GPTCONA (EVA) and GPTCONB (EVB). Now, open “DSP24_Ev.h” and add the declaration for GPTCONA as follows: #include "DSP24_Ev.h" volatile unsigned int *T1CNT=(void*)0x7401; /* GP timer 1 counter reg */ volatile unsigned int *T1CMPR=(void*)0x7402; /* GP timer 1 compare reg */ volatile unsigned int *T1PR=(void*)0x7403; /* GP timer 1 period reg */ // GPTCONA volatile union GPTCONA_REG *GPTCONAbits=(void*)0x7400; GPTCONA declaration

//T1CON volatile union T1CON_REG *T1CONbits=(void*)0x7404; // EVAIMRA volatile union EVAIMRA_REG *EVAIMRAbits=(void*)0x742C; // EVAIFRA volatile union EVAIFRA_REG *EVAIFRAbits=(void*)0x742F;

66

THE EVENT MANAGERS

5.1.2 Modifying the Timer 1 Initialization function We now need to modify initTimer1() function to configure the timer to produce a pulse width modulated signal on pin T1PWM/T1CMP. Open “DSP24_Timer1.c” and modify the code in initTimer1() as follows: #include "DSP24_Ev.h" #include "DSP24_Core.h" void initTimer1(void) { GPTCONAbits->bit.T1PIN=0x2; // T1CMP Compare output active high GPTCONAbits->bit.T2PIN=00; // T2CMP Comp output forced low (not used) GPTCONAbits->bit.TCOMPOE=1; // Enable all GP Timer Compare outputs GPTCONAbits->bit.T1TOADC=00; // Do not start ADC with Timer1 GPTCONAbits->bit.T2TOADC=00; // Do not start ADC with Timer2 *T1CNT=0; *T1PR=30000; *T1CMPR=15000;

Added code

T1CONbits->bit.rsvd1=0; // reserved T1CONbits->bit.TCMPREN=1; // Enable Timer Compare T1CONbits->bit.TCLD=00; // reload compare reg on underflow T1CONbits->bit.TMODE=01; // Continuous Up/Down mode T1CONbits->bit.TPS=000; // Input Clock PreScale /x1 T1CONbits->bit.rsvd2=0; T1CONbits->bit.rsvd3=0; T1CONbits->bit.TCLKS=00; // Internal Clock source select T1CONbits->bit.SOFT=0; T1CONbits->bit.FREE=0; // immediate stop on emulation suspend EVAIFRAbits->bit.T1PINT=1; // EVAIFRAbits->bit.T1CINT=1; // EVAIFRAbits->bit.T1UFINT=1;// EVAIFRAbits->bit.T1OFINT=1;//

clear clear clear clear

EVAIMRAbits->bit.T1PINT=1; // EVAIMRAbits->bit.T1CINT=1; // EVAIMRAbits->bit.T1UFINT=1;// EVAIMRAbits->bit.T1OFINT=1;//

enable enable enable enable

Timer1 Timer1 Timer1 Timer1 Timer1 Timer1 Timer1 Timer1

Per. Match interrupt flag Compare interrupt flag UnderFlow interrupt flag OverFlow interrupt flag Period Match interrupt Compare interrupt UnderFlow interrupt OverFlow interrupt

*IMR|=0x0002; //enable core level interrupt 2 T1CONbits->bit.TENABLE=1; // Enable the timer }

The new code involves the configuration of GPTCONA. If you take a look in TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals, you will find that there are two registers configuring GP Timers (GPTCONA and GPTCONB) having identical 67

THE EVENT MANAGERS

bit definitions for all four timers in EVA and EVB. Although in project “Timer1”, we did not configure the general purpose timer register, it should be a solid policy to configure it, even if you do not intend to use pulse width modulation on the T1PWM/T1CMP pin. 5.1.3 General Purpose Timer Control Register A The general purpose timer control register A (GPTCONA) bitwise outline is shown in Figure 5-2.

Figure 5-2. Register GPTCONA bits2.

The T1PIN0-1 bits. Bits 0 and 1 in GPTCONA configure the polarity of the timer 1 compare pin. We choose active high (“10”). Check the manual for more options. The T2PIN0-1 bits. Bits 2 and 3 in GPTCONA configure the polarity of the timer 2 compare pin. It is configured as forced low” (“00”), since timer 2 does not concern this example. The TCMPOE bit. Bit 6 in GPTCONA enables timer 1 and timer 2 compare outputs. Obviously, you should set this bit to “1” if you want to get a pulse out of the compare pins. The T1TOADC0-1 bits. Bits 7-8 in GPTCONA define the actions of timer 1 events upon the analog to digital converter. As mentioned earlier, the ADC may be forced to start its conversion operation by a GP Timer. Since we are not using the ADC, these bits are set “00” (no event triggers ADC). Check your manual for more options. The T2TOADC0-1 bits. Bits 9-10 in GPTCONA define the actions of timer 1 events upon the analog to digital converter. The ADC conversion sequence may be triggered by events related to timer 2. Again, since we are not using the ADC, these bits are set “00” (no event triggers ADC). 2

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

68

THE EVENT MANAGERS

The T1STAT bit. Bit 13 in GPTCONA is a read -only bit providing information on the timer counter status (“0” – counting downward, “1” – counting upward). The T2STAT bit. Same as bit 14 for timer 2. 5.1.4 I/O Pin Configuration Never forget that most I/O pins on the DSP are multiplexed. Specifically, the T1PWM/T1CMP pin can either be T1CMP, or just another general I/O pin (i.e., B4). It is important to configure your I/O before using it. Normally, I/O should be configured after the system initialization sequence. The best way is to create a function initIO(). Let us append file “DSP24_Sys.c”. Open the file, and add the following function: void initIO(void) { MCRAbits->bit.T1PWMT1CMP_GPIOB4=1; // T1CMP pin set to PWM }

A “1” is written to the appropriate bit-field of structure MCRAbits in order to configure pin T1PWM/T1CMP/GPIOB4 as a timer 1 output (primary function). This is the first time we are accessing MCRAbits from “DSP24_Sys.c”, therefore you should add an include statement right at the beginning of the file: #include "DSP24_Gpio.h"

Save the file. Now it is time to make a few changes in “T1main.c” and we will be ready to go! 5.1.5 The main() function The modifications required for main() are the addition of a call to initIO(), as well as the external declaration of the function. Modify the code as follows: extern void initSystem(void); extern void initTimer1(void); extern void initIO(void); void main() {

External declaration of initIO()

initSystem(); initIO(); initTimer1(); asm(" CLRC INTM"); while (1) {

Call to initIO() right after System Initialization // enable Interrupts

69

THE EVENT MANAGERS } }

Save all changes to your files and to the project. The project browser window should now look like the one in Figure 5-3.

Figure 5-3. Project browser window for “t1pwm”.

Do a Rebuild all and download the program to the DSP. Our next task is to verify the PWM signal from the T1PWM/T1CMP pin. So, get your oscilloscope ready. 5.1.6 Observing the Pulse Width Modulated signal on the T1PWM/T1CMP pin Before we run the program, we will have to physically locate the T1PWM/T1CMP pin on the DSP board. Figure 5-4 shows what you can also discover in the eZdsp™ LF2407A– Technical Reference manual by Spectrum Digital Incorporated.

Figure 5-4. P2/P8 Header connections (T1PWM is 15 and GND is 40)3.

3

eZdsp™ LF2407A– Technical Reference (Spectrum Digital)

70

THE EVENT MANAGERS

You may now connect your oscilloscope probe to pin 15. Reference ground is located on pin 40 (you may alternatively use pin 39) in the header. If your program is already loaded in the DSP, do a CPU Reset and a Go Main afterwards. Remove breakpoints (if any) from the code, and do a Run. Hopefully, if all went well, you should be able to see the pulse of Figure 5-5 on your oscilloscope display.

Figure 5-5. Oscilloscope display for the T1PWM/T1CMP pin (500us/div and 5V/div).

There’s nothing like seeing the results you expected to see! Let us examine our beautiful signal for a while. Notice that the pin stays “high” for a period of time equal to the one during which, it stays “low”. This is happening because the compare register (T1CMPR) is loaded with 15000, which is half of 30000, i.e., the value of the period register (T1PR). Recall that we chose a continuous up/down counting mode. This means that the counter will run from 0 to 30000 and then countdown to 0 again, thereby repeating this cycle for as long as the timer stays enabled. The pin changes state whenever the counter “meets” 15000. Actually, it stays “high” from the moment the counter starts until it hits 15000 for the first time. Afterwards, it switches to “low” for a time interval equal to 2x15000=30000 (counter reaches 30000 and then descends towards 0. Upon “hitting” 15000 during the descend count, the polarity changes again to “high”) and the cycle repeats all over again (“high”-“low”-“high”-etc.). Consequently, the pin stays “high”, as much as it stays “low”, for an interval of 30000 count-up/down steps. The overall period of the signal is 60000 steps. As shown on the scope display, in terms of time units, this corresponds to a signal period of approximately 1.5 ms. We may deduce that 1 increment/decrement step of the counter corresponds to 1.5 ms / 30000 = 0.025 μsec (approximately 40 MHz count increment/decrement frequency). Hey, remember the 71

THE EVENT MANAGERS

clocking signal? We specified internal clocking (CPU) for the timer with /x1 prescaling, so it would make sense that the counter should be following this frequency. In fact, the CPU clock of the 2407A runs at about 40 MHz (recall PLL prescale factor, x4). Once again, always verify your calculations with a scope. Although it only takes basic arithmetic, you better be safe than sorry. Having a lot of parameters to play with is great on one hand, but is also the usual reason for miscalculations.

Figure 5-6. Diagram of the timer 1 counter value and the T1PWM pulse through time.

It is highly recommended to “play” with the timer’s settings. Notice that you are free to change the value of T1CMPR at any time in order to achieve different duty cycles with your signal while the timer is running. Just keep in mind that any change in the T1CMPR value will become effective (i.e., copied from the shadow register to T1CMPR) after the next period match or underflow.

5.2 Using the Compare Units for Pulse Width Modulation in a new project So far, we have seen PWM signal generation with the use of a GP Timer. EVA and EVB offer six specialized units called compare units to accommodate a wide variety of “tastes” for pulse width modulated signal generation. Each compare unit is able to drive two PWM outputs producing either complementary (“active high”-“active low”), or same signals. Think of the compare units as the actual mechanism to use if you want to “do business” with PWM. EVA contains three compare units, driving pins PWM1, PWM2 (unit 1), PWM3-PWM4 (unit 2), PWM5-PWM6 (unit 3). Accordingly, EVB contains compare units 4-6 (pins PWM7-PWM12). Note that timer 1 is the time base for compare units 1-3 (EVA), whereas timer 3 is the time

72

THE EVENT MANAGERS

base for compare units 4-6 (EVB). Get ready to see everything in action throughout the next example. Let’s move on to a new project. Create a folder named “comp1pwm” and copy files “DSP24_Core.h”, “DSP24_Core.c”, “DSP24_Sys.c”, “DSP24_DefaultISR.h”, “DSP24_DefaultISR.c”, “DSP24_Ev.h”, “DSP24_Ev.c”, “DSP24_Gpio.h”, “2407_cmd.cmd” and “cvectors.asm”. Now, create a new project named “comp1pwm” in the new folder. Then, create a new file for your main() function named “comp1main.c” and type the following: extern void initSystem(void); extern void initPwm(void); extern void initIO(void); void main() { initSystem(); initIO(); initPwm(); asm(" CLRC INTM"); while (1) { }

// enable Interrupts

}

Notice that we removed the call to function initTimer1(), and replaced it with the call to function initPwm(). This function will configure the compare unit 1 and timer 1 configuration/control registers; always remember that compare units in EVA receive clocking from timer 1 (as opposed to compare units in EVB, clocked by timer 3). 5.2.1 Configuring Compare Unit 1 in EVA By now you should be used to the fact that the use of additional devices on the DSP, means more configuration and control registers involved in your code. Let us first update the “DSP24_Ev.h” and “DSP24_Ev.c” files with the appropriate register declarations and definitions. To setup compare unit 1 properly, we will have to access the compare configuration control register (COMCONA), the compare action control register (ACTRA), the compare register (CMPR1) and possibly (we shall see what that means), the dead-band control register (DBTCONA). The setup process for each compare unit is identical; therefore, the code that follows can be easily modified to initialize any of the four compare units in both event managers. However, it is always wise to read the manual before any attempt to write code that accesses control registers.

73

THE EVENT MANAGERS

It is time to update the “DSP24_Ev.h” and “DSP24_Ev.c” files with additional definitions and declaration. Open file “DSP24_Ev.h” and add the following definitions: // COMpare Control Register A (COMCONA) struct COMCONA_BITS { unsigned int rsvd1:8; // 0-7 Reserved unsigned int PDPINTASTAT:1; // 8 PDPINTA pin status unsigned int FCOMPOE:1; // 9 COMPare Output Enable unsigned int ACTRLD:2; // 10-11 Action Control Register Reload unsigned int SVENABLE:1; // 12 Space Vector PWM mode enable unsigned int CLD:2; // 13-14 Compare register reloaD condition unsigned int CENABLE:1; // 15 Compare Enable }; union COMCONA_REG { unsigned int all; struct COMCONA_BITS bit; }; extern volatile union COMCONA_REG *COMCONAbits; // Compare Action Control Register ACTRA (ACTRA) struct ACTRA_BITS { unsigned int CMP1ACT:2; // 0-1 Action on Compare Output Pin 1 unsigned int CMP2ACT:2; // 2-3 Action on Compare Output Pin 2 unsigned int CMP3ACT:2; // 4-5 Action on Compare Output Pin 3 unsigned int CMP4ACT:2; // 5-7 Action on Compare Output Pin 4 unsigned int CMP5ACT:2; // 8-9 Action on Compare Output Pin 5 unsigned int CMP6ACT:2; // 10-11 Action on Compare Output Pin 6 unsigned int D0:1; // 12 Basic Space Vector bit D0 unsigned int D1:1; // 13 Basic Space Vector bit D1 unsigned int D2:1; // 14 Basic Space Vector bit D2 unsigned int SVRDIR:1; // Space Vector PWM rotation direction }; union ACTRA_REG { unsigned int all; struct ACTRA_BITS bit; }; extern volatile union ACTRA_REG *ACTRAbits; // DBTCONA struct DBTCONA_BITS { unsigned int rsvd1:2; // 0-1 reserved unsigned int DBTPS:3; // 2-4 Dead-Band timer prescaler unsigned int EDBT1:1; // 5 Dead-Band timer 1 enable(PWM1-PWM2) unsigned int EDBT2:1; // 6 Dead-Band timer 2 enable (PWM3-PWM4) unsigned int EDBT3:1; // 7 Dead-Band timer 3 enable (PWM5-PWM6) unsigned int DBT:4; // 8-11 Dead-Band timer period unsigned int rsvd2:4; // 12-15 reserved }; union DBTCONA_REG { unsigned int all; struct DBTCONA_BITS bit; }; extern volatile union DBTCONA_REG *DBTCONAbits; extern volatile unsigned int *CMPR1; // CoMPare Register for Compare unit1

74

THE EVENT MANAGERS

We are almost finished! Open “DSP24_Ev.c” and add the declarations of the register structures we have just created as shown highlighted in the code below: #include "DSP24_Ev.h" volatile unsigned int *T1CNT=(void*)0x7401; /* GP timer 1 counter reg */ volatile unsigned int *T1CMPR=(void*)0x7402; /* GP timer 1 compare reg */ volatile unsigned int *T1PR=(void*)0x7403; /* GP timer 1 period */ volatile unsigned int *CMPR1=(void*)0x7417; /* Comp.un1 Compare register // GPTCONA volatile union GPTCONA_REG *GPTCONAbits=(void*)0x7400; Compare register 1

//T1CON volatile union T1CON_REG *T1CONbits=(void*)0x7404; // EVAIMRA volatile union EVAIMRA_REG *EVAIMRAbits=(void*)0x742C; // EVAIFRA volatile union EVAIFRA_REG *EVAIFRAbits=(void*)0x742F; // COMCONA volatile union COMCONA_REG *COMCONAbits=(void*)0x7411; // ACTRA volatile union ACTRA_REG *ACTRAbits=(void*)0x7413; // DBTCONA volatile union DBTCONA_REG *DBTCONAbits=(void*)0x7415;

Compare control register A

Compare Action control register A Dead-Band unit control register A

Unfortunately, the number of registers involved in the setup of the compare units is relatively high. The truth is that NOBODY remembers these structures; in fact, even the most experienced programmers need to use manuals and take a deep breath before initializing the compare unit (or several other peripherals on the 2407A). The best policy is to keep your examples and code templates well organized for future use and possible modifications, in order not to have to repeatedly go through the same painful process. If you are feeling better, let us move on to briefly examine the new structures. 5.2.2 The Compare Control Register A (COMCONA) The 2407A includes two compare control registers, COMCONA and COMCONB. Think of them as the primary registers demanding access when programming the compare units. Obviously, COMCONA configures compare units 1 and 2 in EVA, whereas COMCONB configures compare units 3 and 4 in EVB. Both registers are identical in structure; hence, 75

THE EVENT MANAGERS

whatever applies for COMCONA, also applies to COMCONB. Figure 5-7 shows the bitwise structure of COMCONA.

Figure 5-7. Compare Control Register A (COMCONA) 4.

The PDPINTASTATUS bit. Bit 8 should be the least of our concerns for the time being. In the 2407A (but not in the 2407), this bit reflects the status of the power drive interrupt protection (PDPINTA) pin. Under certain conditions, you may wish to poll it in order to realize wake ups from low power modes. The FCOMPOE bit. Bit 9 enables/disables PWM outputs. Note here that PWM outputs are not related to and should not be confused with the timer compare outputs. The ACTRL0 and ACTRL1 bits. Bits 10 and 11 define the action control register reload condition. As we will shortly be seeing, the compare units offer a wide variety of methods to configure the PWM signal. Action control registers play a significant part in this. The action control register may be accessed during PWM operation. In other words, you are free to reconfigure your signal during operation by reloading this register. If ACTRL0 and ACTRL1 are “00”, reloading occurs on a timer 1 underflow; if “01”, reloading occurs on period match or underflow; while if “10”, reloading occurs immediately. Remember, that you may change the values of ACTRA or ACTRB at anytime during compare unit operation, but the values will remain stored inside the shadow register until the appropriate reloading condition will be met. In our example, reloading will be occurring on timer 1 underflow, although ACTRA will not be changed at any point following its initialization. The SVENABLE bit. Bit 12 enables/disables the space vector PWM mode. Space vector mode is useful in driving three-phase motors with a single DC power supply by managing the 4

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

76

THE EVENT MANAGERS

states of six transistors. For the current example, space vector PWM is disabled, but take your time and read the manual on how to setup such waveforms with your DSP. The CLD0 and CLD1 bits. Bits 13 and 14 define the reload condition of the compare register. You may reload the compare value at any point in order to shape your signal’s duty cycle during PWM operation. If “00”, reloading occurs on underflow; if “01” reloading occurs on period match and on underflow; if “10”, reloading occurs immediately. In our example, the CLD bits are configured for a reload on underflow (“00”). The CENABLE bit. Bit 15 enables the compare operation. 5.2.3 The Compare Registers (CMPR1-6) Much like in the case of PWM generation with a timer, every compare unit utilizes a compare register of its own. There are 6 compare registers (CMPR1-6), each one corresponding to a pair of PWM pins. Specifically, in the current example, CMPR1 corresponds to PWM1 and PWM2 outputs (compare unit 1) and it is used for constant comparison against the contents of the timer 1 counter (T1CNT). Upon a compare event, the compare unit may trigger an interrupt, and/or perform several other actions defined in the compare action control registers (ACTRA, ACTRB). 5.2.4 The Compare Action Control Registers (ACTRA and ACTRB) The compare action control registers, as their name implies, define the action that takes place when a compare event occurs. Once again, to accommodate multiple needs in PWM signal generation, the designers of the event managers have incorporated a wide variety of possible configurations. The bitwise outline of ACTRA is illustrated in Figure 5-8 (ACTRB is identical in every aspect).

Figure 5-8. Compare action control register A (ACTRA)5.

5

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

77

THE EVENT MANAGERS

The CMPxACTy bit pairs. Bits 0-12 specify the actions occurring on the compare output pins of compare units 1, 2 and 3 (obviously, ACTRB provides action control for compare units 4, 5 and 6). Assume we want to configure the action for the first output pin (PWM1) of compare unit 1, we should then setup a 2-bit value for CMP1ACT0, corresponding to bits 0 and 1 in ACTRA. Our possible options are: 00 : Forced Low. 01 : Active Low. 10 : Active High. 11 : Forced High. We may use these options to configure any PWM output. A common approach is to configure the pins to produce complementary pulses. For example, you may setup PWM1 pin to active high (CMP1ACT1=1, CMP1ACT0=0) and PWM2 to active low (CMP2ACT1=0, CMP2ACT0=1), but you also may use any configuration that suits your application (i.e., set both pins to active low or active high). The D0-D2 bits. Bits 12-14 are the base space vector bits. These bits are used only for space vector PWM generation. Once again, we will not be dealing with this technique; you may study more about this technique from the manual. The SVRDIR bit. Bit 15 (SVRDIR) defines the direction of the space vector rotation. A 0value corresponds to positive, and a 1-value corresponds to negative.

5.2.5 The Dead-band Timer Control Register A (DBTCONA) Recall that EVA and EVB contain two dead-band units (one for each event manager). The dead-band units allow you to create artificial “dead-bands” upon the edges of the PWM signals. The term “dead-band” actually refers to an interval between a switch from HIGH to LOW (or vice-versa) in a signal’s state, during which, the signal has no actual value (i.e., floating). The reason that the DSP provides this kind of accessory to accompany the compare units is that, when a pair of complementary PWM pins is switching state, the switch occurs at the same period of time; this means that for this particular time interval, PWM1 for example, will be “rising” from LOW to HIGH whereas PWM2 will be “falling” from HIGH to LOW and for that small amount of time, neither of them represents a valid value for the device driven by the generated signals. There are cases where that might be a significant problem. To cope with this type of situations, we may program the dead-band to delay the rising or falling edge of the pulses in order to make sure that, at least one of the two signals has a valid HIGH or LOW value so that the driven device is not “confused”. If you look at Figure 5-9, the DB unit 78

THE EVENT MANAGERS

acts much like a “traffic controller” between the symmetric/asymmetric waveform generator and the final output logic that produces the signals on PWM1-6 pins. If the DB is not enabled, the signals in PWM1-2, will be perfectly complementary in terms of phase, whereas, if enabled, the signals will introduce a time shift, according to the configuration of the deadband control register (A or B). In our example we will not be using the EVA dead-band unit; therefore, we configure accordingly the corresponding register (DBTCONA).

Figure 5-9. The Dead-band Unit in EVA6.

Dead-band units can be configured with their own control registers (as I am sure you have already guessed). Regardless of whether you are using a DB unit or not, you should always include the configuration of the appropriate registers somewhere among the initialization steps. Figure 5-10 shows the outline for the dead-band timer control register (DBTCONA). Dead-bands in EVB are configured by DBTCONB, which is identical to DBTCONA in every aspect. You may configure six dead-band timers, one for each compare unit (one for each pair of PWM pins). All DB timers in an event manager share the same period value, but can be independently enabled/disabled. Hence, DB timer 1, 2 and 3 configured by DBTCONA, have the same period.

6

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

79

THE EVENT MANAGERS

Figure 5-10. The Dead-Band Timer Control Register (DBTCONA)7.

The DBTPS0-2 bits. Bits 2, 3 and 4 in DBTCONA define the timer’s prescaling factor. A value of “000” corresponds to /x1, “001” to /x2, “010” to /x4, …, “111” to /x32. The EDBT1 bit. Bit 5 enables/disables dead-band timer 1. The EDBT2 bit. Bit 6 enables/disables dead-band timer 2. The EDBT3 bit. Bit 7 enables/disables dead-band timer 3. The DBT0 (LSB)-DBT3 (MSB) bits. Bits 8-11 define the period of dead-band timers 1, 2 and 3. 5.2.6 PWM Initialization Routine At last, we are ready to write a routine called initPWM() to initialize PWM operation using compare unit 1. Create a new file named “DSP24_Comp1.c” and type the following: #include "DSP24_Ev.h" #include "DSP24_Core.h" void initPwm() { T1CONbits->all = 0x0000; // clear Timer1 Control register // 1. Configuring General Purpose Timer CONtrol Register A (GPTCONA) GPTCONAbits->bit.T1PIN=0;// timer1 compare output pin forced low GPTCONAbits->bit.T2PIN=0;// timer2 compare output pin forced low GPTCONAbits->bit.T1TOADC=00; // timer1 does not start ADC GPTCONAbits->bit.T2TOADC=00; // timer2 does not start ADC GPTCONAbits->bit.TCOMPOE=00; // disable compare outputs of timer1 timer2 *T1CNT=0; // clearing timer1 counter *T1PR=30000; // setting timer1 period to 30000 (3 ms between Underflows)

7

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

80

THE EVENT MANAGERS // 2. Configuring Timer 1 Control register T1CONbits->bit.rsvd1=0; // reserved T1CONbits->bit.TCMPREN=0; // disable Timer Compare Operation T1CONbits->bit.TCLD=00; // reload compare reg on underflow T1CONbits->bit.TMODE=01; // Continuous Up/Down mode T1CONbits->bit.TPS=000; // Input Clock PreScale /x1 T1CONbits->bit.rsvd2=0; T1CONbits->bit.rsvd3=0; T1CONbits->bit.TCLKS=00; // Internal Clock source select T1CONbits->bit.SOFT=0; T1CONbits->bit.FREE=0; // immediate stop on emulation suspend // 3. Setting up Compare ACtion control Register A(ACTRA) ACTRAbits->bit.CMP1ACT=2; // PWM1 actibe high ACTRAbits->bit.CMP2ACT=1; // PWM2 active low ACTRAbits->bit.CMP3ACT=00; // PWM3 forced low ACTRAbits->bit.CMP4ACT=00; // PWM4 forced low ACTRAbits->bit.CMP5ACT=00; // PWM5 forced low ACTRAbits->bit.CMP6ACT=00; // PWM6 forced low ACTRAbits->bit.D0=0; ACTRAbits->bit.D1=0; ACTRAbits->bit.D2=0; // Basic space vector D2D1D0 is "don't care" ACTRAbits->bit.SVRDIR=0; // S.V. dir is again "don't care" *CMPR1=15000; // setting Compare register to half the timer's period // 4. Setting up COMpare CONfiguration register A COMCONAbits->bit.rsvd1=0; COMCONAbits->bit.FCOMPOE=1; // enable PWM pins COMCONAbits->bit.CLD=00; // Reload Compare regs on Timer1 UnderFlow COMCONAbits->bit.ACTRLD=00; // Reload Actra on Timer1 UnderFlow COMCONAbits->bit.SVENABLE=0; // Disable Space vector COMCONAbits->bit.CENABLE=1; // Enable Comparison // 5. Setting up the deadband unit DBTCONAbits->bit.DBTPS=000; // Deadband DBTCONAbits->bit.EDBT1=0; // disable DB DBTCONAbits->bit.EDBT2=0; // disable DB DBTCONAbits->bit.EDBT3=0; // disable DB DBTCONAbits->bit.DBT=0000; // DB period

timer prescale timer for PWM1 timer for PWM3 timer for PWM5 set to 0.

/x1 and PWM2 pins and PWM4 pins and PWM6 pins

// 6. Configuring - Initiating Interrupts EVAIFRAbits->bit.T1UFINT=1;// clear Timer1 UnderFlow interrupt flag EVAIMRAbits->bit.T1UFINT=1;// enable Timer1 UnderFlow interrupt EVAIFRAbits->bit.CMP1INT=1;// clear Compare1 interrupt flag EVAIMRAbits->bit.CMP1INT=1;// enable Compare1 interrupt *IMR|=0x2; // enabling core level INT2 // 7. Starting Timer1 T1CONbits->bit.TENABLE = 1; }

81

THE EVENT MANAGERS

Notice that we configured GPTCONA and T1CONA, early in this configuration. Keep in mind that whenever a compare unit is used, it is imperative to initialize the corresponding timer (timer 1 for compare units 1-2 or timer 3 for compare units 3-4); this means that you will have to be configuring the relative registers. In our case, timer 1 provides clocking to the compare unit; therefore, we need to initialize it and set a period value. The period register is loaded with 30000 (recall from our previous example that this corresponds to 1.5 ms approximately). The settings of timer 1 are essentially the same with the ones in the previous example (continuous count up/down mode, a period of 30000), except for the TCMPREN bit in T1CON, which is set to “0” in order to disable the timer’s compare operation. You may keep it enabled, but we better focus our interest on the compare unit and its own compare operations and events. As shown shortly, as far as the timer is concerned, the only event of interest is underflow. Let us take a look at ACTRA now. You can see that the 2-bit field CMP1ACT is assigned a value of 2 (“10”-active high) and CMP2ACTis assigned a value of 1 (“01”-active low). This means that, that output pins PWM1 and PWM2 will produce signals exactly opposite in polarity. The rest of the PWM output pins in EVA (PWM3-6) are configured forced low (“00”). The base space vector bits (D0-2), as well as the space vector rotation direction (SVRDIR) bit are assigned a “0”-value (since space vector operation will be disabled, the values are ineffective). Notice the value assignment, just before the configuration of COMCONA: *CMPR1=15000; // setting Compare register to half the timer's period

This is the value assignment for compare register 1. As in our previous example (PWM using the timer 1 compare output), the register is loaded with half the timer’s period. This should result in a PWM signal with a 50% duty cycle. In COMCONA, the FCOMPOE pin is obviously set to “1”, thus enabling PWM outputs. Now, let us take a good look at the following: COMCONAbits->bit.CLD=00; // Reload Compare regs on Timer1 UnderFlow

This value assignment configures the compare unit to reload the compare register on timer 1 underflow. You can also specify a reload on period match, by assigning a 1 (“01”); a setting of 2 (“10”), corresponds to immediate reload; finally, you should never assign a value of 3 (“11”). As mentioned earlier, the DSP allows you to change the parameters of the PWM signal during operation, meaning that you may specify a new value for the compare register (CMPR1-6) at any time during PWM generation in order to manipulate the duty cycle of the signal. This does not necessarily mean that the changes will take effect immediately. Actually, the new value 82

THE EVENT MANAGERS

remains stored in the shadow register that corresponds to CMPR1 (if using compare register 1), until it is eventually copied to CMPR1 upon a specific event (underflow or period match). You can also force the value to be written immediately, but it is not advisable. The most common setting for these bits is a reload on underflow, or both underflow and period match. In quite the same way, you may specify reload events for the compare action control register. In our case, we configure an ACTRA reload upon underflow: COMCONAbits->bit.ACTRLD=00; // Reload Actra on Timer1 UnderFlow

Obviously, you may specify a reload on both period match and underflow by assigning a value of 1 (“01”), or immediately, by assigning a 2 (“10”). Unless you have very specific reasons to modify the compare action control register, you should keep it as initialized and let it simply be refreshed on every underflow. The rest of the configuration of COMCONA involves the disabling of space vector PWM by assigning a “0” to field SVENABLE and the enabling of the compare operation by writing a “1” to field CENABLE. Although not necessary, the unit 1 compare interrupt (CMP1INT) and the timer 1 underflow interrupt (T1UFINT) are enabled. Generally, try to keep enabled as less interrupts as possible. Since both timer 1 underflow and compare 1 interrupts correspond to core level 2 (INT2), the corresponding bit in IMR is unmasked. Finally, in the last line of initPWM(), timer 1 is enabled. 5.2.7 I/O Initialization Since we are going to be using PWM1 and PWM2 pins for pulse width modulation, this calls for modifications in initIO() in file “DSP24_Sys.c”. Open the file, delete the old initIO() and type the following: void initIO(void) { // making Timer1 Compare Output regular I/O MCRAbits->bit.T1PWMT1CMP_GPIOB4=0; // T1CMP pin set as regular I/O // setting up PWM1 and PWM2 for Pulse Width Modulation MCRAbits->bit.PWM1_GPIOA6 = 1; MCRAbits->bit.PWM2_GPIOA7 = 1; }

This time, the time 1 compare output is configured as standard I/O pin, whereas PWM1 and PWM2 are configured as compare unit outputs. Save the file, we are almost done.

83

THE EVENT MANAGERS

5.2.8 Interrupt Service Routines To briefly summarize the principles behind interrupt execution in the 2407A, recall that every peripheral corresponds to a core level interrupt. If you take a look at your System and Peripherals reference manual (Table 2-3, Chapter 2), you will find that CMP1INT is a core level 2 interrupt with a peripheral interrupt vector value of 0x0021. We must now include a new case branch in the switch statement for that particular value, inside the INT2_GISR() function. Open DSP24_DefaultISR.c and modify INT2_GISR() as follows: interrupt void INT2_GISR(void) {// GISR for all INT2 level peripherals unsigned int peripheral; asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM"); switch (peripheral) { case 0x0027: // Timer 1 period T1PINT_ISR(); break; case 0x0028: // Timer 1 Compare T1CINT_ISR(); break; case 0x0029: // Timer 1 UnderFlow T1UFINT_ISR(); break; case 0x002A: // Timer OverFlow T1OFINT_ISR(); break; case 0x0021: // Compare 1 CMP1INT_ISR(); break;

Branch to compare 1 specific interrupt service routine

default: break; } // reset the core INT2 flag by writing a "1" in bit 1 *IFR|=2; }

Now that we have added the call to CMP1_ISR(), we can move on to create a declaration for it. Open “DSP24_DefaultISR.h” and add the highlighted declaration in the following code: #ifndef DSP24_DEFAULT_ISR_H #define DSP24_DEFAULT_ISR_H #include "DSP24_Ev.h" #include "DSP24_Core.h"

84

THE EVENT MANAGERS // Timer 1 Period Match interrupt interrupt void T1PINT_ISR(void); // Timer 1 Compare interrupt interrupt void T1CINT_ISR(void); // Timer 1 Underflow interrupt interrupt void T1UFINT_ISR(void); // Timer 1 Overflow interrupt interrupt void T1OFINT_ISR(void);

Compare1 ISR (CMP1INT_ISR)

// Compare 1 Interrupt interrupt void CMP1INT_ISR(void); interrupt void INT1_GISR(void); interrupt void INT2_GISR(void); interrupt void INT3_GISR(void); interrupt void INT4_GISR(void); interrupt void INT5_GISR(void); interrupt void INT6_GISR(void); #endif

Save the file. We may now implement the function in “DSP24_DefaultISR.c”. Open the file and add function CMP1INT_ISR() below timer 1 interrupt service routines: // Compare 1 Interrupt interrupt void CMP1INT_ISR(void) { EVAIFRAbits->bit.CMP1INT=1; // Clear Compare 1 interrupt flag asm(" NOP"); }

5.2.9 Compiling and executing Save all your files and make sure you have added them to project comp1pwm. Your project browser window should now look like the one in Figure 5-11:

85

THE EVENT MANAGERS

Figure 5-11. Browser window for Project “comp1pwm”.

Compile the project and download to the DSP. Do a Reset CPU and then a Go Main. Before you run your project freely, it is better to verify that the interrupts are being executed as expected. Go to “DSP24_DefaultISR.c” and add a breakpoint inside CMP1INT_ISR() and T1UFINT_ISR(), as shown in Figure 5-12.

Figure 5-12. Breakpoints inside the T1UF and CMP1 ISRs.

Now, run the program. If everything went okay, you should be having two consecutive “stops” inside the CMP1INT_ISR() routine, followed by one inside T1UFINT_ISR(); this sequence of events should repeat indefinitely. 86

THE EVENT MANAGERS

A good idea would be to verify the signals on PWM1 and PWM2. The locations of PWM1 and PWM2 on the I/O header are shown in Figure 5-13.

Figure 5-13. PWM1 and PWM2 are 9 and 10 on I/O header of the DSP board8.

Connect the oscilloscope probes on pins 9 and 10 (PWM1 and PWM2). Use pin 39 or 40 for ground. Remove the breakpoints from the code, do a reset and run it. The compare unit should now be producing exactly the same pulse produced on the timer 1 compare output in the previous example of this chapter. The signal generated on PWM2 should be displaying a complementary (active low) waveform with exactly the same period and duty cycle. Figures 514 and 5-15 show the waveforms generated on the two PWM pins. If you are looking at the expected waveforms, I am sure you are as happy as I am! Our signal has exactly the same period with the one generated on the timer 1 compare pin in the previous example (1.5 ms) and a crystal-clear 50% duty cycle on both PWM1 and PWM2 pins (disregard a little bit of a phase difference, since I used a single channel scope to probe both pins). Once again, if we do the math, 750 μs correspond to 15,000 counts of T1CNT. This gives us about 0.025 μs per count up/down, corresponding to a 40 MHz frequency, which is exactly the CPU clock frequency.

8

eZdsp™ LF2407A– Technical Reference (Spectrum Digital)

87

THE EVENT MANAGERS

Figure 5-14. Signal output on PWM1 (Active High).

Figure 5-15. Signal output on PWM2 (Active Low).

5.3 Using the EVA Capture Units As mentioned earlier, the 2407A is equipped with six capture units, (capture 1, 2, 3, 4, 5 and 6), three in each event manager. The capture units, as their name implies, can be configured to detect rising or falling pulse edges on their input pins. In fact, they can store time-related information in special FIFO (First-In-First-Out) registers. The capture units, much like the compare units, require a general purpose timer for clocking. It can be timer 1 or timer 2 for EVA, and timer 3 or timer 4 in the case of EVB. The selected timer must be common for 88

THE EVENT MANAGERS

both capture units 1 and 2 (or capture units 4 and 5 in the case of EVB). Figure 5-16 shows the block diagram of the capture units.

Figure 5-16. Block Diagram of the Capture Units9.

Imagine you are trying to find the time elapsed between two pulses, or that you are trying to measure the length of a pulse from an ultrasonic echo transducer. A typical strategy would be to setup a timer to periodically poll a general I/O pin for changes in its state. As soon as you see a rise or fall in the signal, you change the value of a state variable and start a counter. The program increases the counter at periodic intervals until it “sees” a new rise or fall in the signal. This is a very reasonable way of doing things, but not the DSP way! Instead of using extensive code that takes up a lot of CPU instruction cycles (and may not even capture every change in the state of the signal), you may program the capture unit to do all this for you. All you have to do is to wait for a capture interrupt and go “pick up” the time that a rising/falling edge occurred from the FIFO registers. No extra code, no instruction execution delays! The FIFO registers can give you more than one time instances in which a 9

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

89

THE EVENT MANAGERS

change in the signal occurred and the only thing you need to do is a simple subtraction or multiplication, depending on the kind of information you are looking for. Additionally, you may configure the quadrature encoder pulse circuit (one for each event manager) to use the capture pins (CAP1 and CAP2 for EVA, CAP4 and CAP5 for EVB) to gain information about the rotation direction and speed of a motor, assuming the presence of rotary encoders. Figure 5-17 shows the block diagram of the QEP circuit in EVB.

Figure 5-17. Block Diagram of the Quadrature Encoder Pulse circuit in EVA10.

5.3.1 Using the Capture Units for Rising/Falling edge detection in a new project It is time for a new example. I am sure that by now, certain moves have become a routine. Create a new folder named “Capunit” and copy files “2407_cmd.cmd”, “cvectors.asm”, ”DSP24_Ev.h”, “DSP24_Ev.c”, “DSP24_Core.h”, “DSP24_Core.c”, “DSP24_Default_ISR.h”, “DSP24_DefaultISR.c”, “DSP24_Gpio.h”, “DSP24_Gpio.c” and “DSP24_Sys.c”. Open the editor and create a new C file for main() function, named “cap1main.c”; type the following code: extern void initSystem(void); extern void initCap1(void); extern void initIO(void); void main() {

10

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

90

THE EVENT MANAGERS

initSystem(); initIO(); initCap1(); asm(" CLRC INTM"); while (1) { }

// enable Interrupts

}

That is nothing really new, actually. It is practically the same main() routine with the previous example, take-away the call to initPwm() and replace it with the call to initCap1(). We must now focus on defining the structures corresponding to the capture unit registers and write some initialization code inside initCap1(). 5.3.2 Defining Structures for the Capture Unit Registers Once again, we need to expand our header files a little bit. As stated earlier, the capture units require clocking from a GP Timer. In EVA, we may choose either timer 1, or timer 2. By using timer 2, we actually give ourselves a good chance to write the structure definitions and declarations for timer 2-related configuration registers. It is better to expand the header files with the first chance that you get. Open file “DSP24_Ev.h” and add the highlighted definitions in the listing that follows (I have included the entire file so that you may get an idea on how to keep it somehow organized. Unfortunately, event manager-related header files tend to be huge): #ifndef DSP24_EV_H #define DSP24_EV_H // General Purpose Timer Control struct GPTCONA_BITS { unsigned int T1PIN:2; unsigned int T2PIN:2; unsigned int rsvd1:2; unsigned int TCOMPOE:1; unsigned int T1TOADC:2; unsigned int T2TOADC:2; unsigned int rsvd2:2; unsigned int T1STAT:1; unsigned int T2STAT:1; unsigned int rsvd3:1;

Register A (GPTCONA) // // // // // // // // // //

0-1 Polarity of GP timer 1 compare 2-3 Polarity of GP timer 2 compare 4-5 Reserved 6 CoMPare Output Enable 7-8 Start ADC with Timer 3 9-10 Start ADC with Timer 4 11-12 Reserved 13 Timer 3 STATus 14 Timer 3 STATus 15 reserved

}; union GPTCONA_REG { unsigned int all; struct GPTCONA_BITS bit; };

91

THE EVENT MANAGERS

extern volatile union GPTCONA_REG *GPTCONAbits; //Timer 1 CONtrol register (T1CON) struct T1CON_BITS { unsigned int rsvd1:1; // 0 Reserved unsigned int TCMPREN:1; // 1 Timer Compare Enabled unsigned int TCLD:2; // 2-3 Compare register reload condition unsigned int TCLKS:2; // 4-5 Clock Source Select unsigned int TENABLE:1; // 6 Timer Enable unsigned int rsvd2:1; // 7 Reserved unsigned int TPS:3; // 8-10 Input Clock Prescaling unsigned int TMODE:2; // 11-12 Count mode selection unsigned int rsvd3:1; // 13 reserved unsigned int SOFT:1; // 14 EMUlation control bit 0 (soft) unsigned int FREE:1; // 15 EMUlation control bit 1 (free) }; union T1CON_REG { unsigned int all; struct T1CON_BITS bit; }; extern volatile union T1CON_REG *T1CONbits; //T2CON struct T2CON_BITS unsigned int for own) unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int

Timer2 Control Register

{ T1PERSEL:1; // 0 Select Period register (1 for T1PR, 0 TCMPREN:1; // 1 Timer Compare Enables TCLD:2; // 2-3 Compare register reload condition TCLKS:2; // 4-5 Clock Source Select TENABLE:1; // 6 Timer Enable T2SWT1:1; // 7 if 1, Timer2 starts with Timer1 TPS:3; // 8-10 Input Clock Prescaling TMODE:2; // 11-12 Count mode selection rsvd3:1; // 13 reserved SOFT:1; // 14 EMUlation control bit 0 (soft) FREE:1; // 15 EMUlation control bit 1 (free)

}; union T2CON_REG { unsigned int all; struct T2CON_BITS bit; }; extern volatile union T2CON_REG *T2CONbits; /* Interrupt Mask Register bit definitions */ struct EVAIMRA_BITS { unsigned int PDPINTA:1; // 0 Enable PDPINTA unsigned int CMP1INT:1; // 1 Enable unsigned int CMP2INT:1; // 2 Enable unsigned int CMP3INT:1; // 3 Enable unsigned int rsvd1:3; // 4-6 reserved unsigned int T1PINT:1; // 7 Enable unsigned int T1CINT:1; // 8 Enable unsigned int T1UFINT:1; // 9 Enable

92

THE EVENT MANAGERS unsigned int unsigned int

T1OFINT:1; rsvd2:5;

// 10 // 11-15

Enable reserved

}; union EVAIMRA_REG { unsigned int all; struct EVAIMRA_BITS bit; }; extern volatile union EVAIMRA_REG *EVAIMRAbits; //EVAIMRB struct EVAIMRB_BITS { unsigned int T2PINT:1; unsigned int T2CINT:1; unsigned int T2UFINT:1; unsigned int T2OFINT:1; unsigned int rsvd1:12; };

// // // // //

0 1 2 3 4-15

EVA Interrupt Mask Register B (contains masks for Timer2 interrupts)

Enable Enable Enable Enable reserved

union EVAIMRB_REG { unsigned int all; struct EVAIMRB_BITS bit; }; extern volatile union EVAIMRB_REG *EVAIMRBbits; // EVAIMRC (Capture 1,2,3 interrupt masks) struct EVAIMRC_BITS { unsigned int CAP1INT:1; // 0 Enable/Disable CAP1 interrupt unsigned int CAP2INT:1; // 1 Enable/Disable CAP2 interrupt unsigned int CAP3INT:1; // 2 Enable/Disable CAP3 interrupt unsigned int rsvd1:13; // 3-15 reserved }; union EVAIMRC_REG { unsigned int all; struct EVAIMRC_BITS bit; }; extern volatile union EVAIMRC_REG *EVAIMRCbits; /* Interrupt Flag Registers bit definitions */ //EVAIFRA struct EVAIFRA_BITS { unsigned int PDPINTA:1; // 0 Flag PDPINTA unsigned int CMP1INT:1; // 1 Flag unsigned int CMP2INT:1; // 2 Flag unsigned int CMP3INT:1; // 3 Flag unsigned int rsvd1:3; // 4-6 reserved unsigned int T1PINT:1; // 7 Flag unsigned int T1CINT:1; // 8 Flag unsigned int T1UFINT:1; // 9 Flag unsigned int T1OFINT:1; // 10 Flag unsigned int rsvd2:5; // 11-15 reserved };

EVA Interrupt Mask Register C (contains masks for Capture Unit 1,2 and 3 interrupts)

union EVAIFRA_REG { unsigned int all; struct EVAIFRA_BITS bit;

93

THE EVENT MANAGERS }; extern volatile union EVAIFRA_REG *EVAIFRAbits; //EVAIFRB struct EVAIFRB_BITS { unsigned int T2PINT:1; unsigned int T2CINT:1; unsigned int T2UFINT:1; unsigned int T2OFINT:1; unsigned int rsvd1:12; };

// // // // //

0 Flag 1 Flag 2 Flag 3 Flag 4-15

EVA Interrupt Flag Register B (contains flags for Timer2 interrupts)

reserved

union EVAIFRB_REG { unsigned int all; struct EVAIFRB_BITS bit; }; extern volatile union EVAIFRB_REG *EVAIFRBbits; //EVAIFRC (CAPture 1,2,3 Interrupt Flags) struct EVAIFRC_BITS { unsigned int CAP1INT:1; // 0 CAP1 Flag unsigned int CAP2INT:1; // 1 CAP2 Flag unsigned int CAP3INT:1; // 2 CAP3 flag unsigned int rsvd1:13; // 3-15 reserved }; union EVAIFRC_REG { unsigned int all; struct EVAIFRC_BITS bit; }; extern volatile union EVAIFRC_REG *EVAIFRCbits;

EVA Interrupt Flag Register C (contains flags for Capture Unit 1,2 and 3 interrupts)

// COMpare Control Register A (COMCONA) struct COMCONA_BITS { unsigned int rsvd1:8; // 0-7 Reserved unsigned int PDPINTASTAT:1; // 8 PDPINTA pin status unsigned int FCOMPOE:1; // 9 COMPare Output Enable unsigned int ACTRLD:2; // 10-11 ACTion Register Reload Condition unsigned int SVENABLE:1; // 12 Space Vector PWM mode enable unsigned int CLD:2; // 13-14 Compare register reloaD condition unsigned int CENABLE:1; // 15 Compare Enable }; union COMCONA_REG { unsigned int all; struct COMCONA_BITS bit; }; extern volatile union COMCONA_REG *COMCONAbits; // Compare Action Control Registers ACTRA (ACTRA) struct ACTRA_BITS { unsigned int CMP1ACT:2; // 0-1 Action on Compare Output Pin 1 unsigned int CMP2ACT:2; // 2-3 Action on Compare Output Pin 2 unsigned int CMP3ACT:2; // 4-5 Action on Compare Output Pin 3 unsigned int CMP4ACT:2; // 5-7 Action on Compare Output Pin 4 unsigned int CMP5ACT:2; // 8-9 Action on Compare Output Pin 5 unsigned int CMP6ACT:2; // 10-11 Action on Compare Output Pin 6

94

THE EVENT MANAGERS unsigned unsigned unsigned unsigned

int int int int

D0:1; D1:1; D2:1; SVRDIR:1;

// // // //

12 Basic Space Vector bit 13 Basic Space Vector bit 14 Basic Space Vector bit Space Vector PWM rotation

D0 D1 D2 direction

}; /* Allow access to the bit fields or entire register */ union ACTRA_REG { unsigned int all; struct ACTRA_BITS bit; }; extern volatile union ACTRA_REG *ACTRAbits; /* Dead-Band Timer Control register bit definitions */ // DBTCONA struct DBTCONA_BITS { unsigned int rsvd1:2; // 0-1 reserved unsigned int DBTPS:3; // 2-4 Dead-Band timer unsigned int EDBT1:1; // 5 Dead-Band timer unsigned int EDBT2:1; // 6 Dead-Band timer unsigned int EDBT3:1; // 7 Dead-Band timer unsigned int DBT:4; // 8-11 Dead-Band timer unsigned int rsvd2:4; // 12-15 reserved }; union DBTCONA_REG { unsigned int all; struct DBTCONA_BITS bit; }; extern volatile union DBTCONA_REG *DBTCONAbits;

prescaler 1 enable 2 enable 3 enable period

Capture Control Register A. Used for the initialization of Capture Units 1,2,3

// Capture Control register A (CAPCONA) struct CAPCONA_BITS { unsigned int rsvd1:2; // 0-1 reserved unsigned int CAP3EDGE:2; // 2-3 Edge Detect for Unit 3 unsigned int CAP2EDGE:2; // 4-5 Edge Detect for Unit 2 unsigned int CAP1EDGE:2; // 6-7 Edge Detect for Unit 1 unsigned int CAP3TOADC:1; // 8 Unit 3 starts the ADC unsigned int CAP12TSEL:1; // 9 GP Timer select-unit 1,2 unsigned int CAP3TSEL:1; // 10 GP Timer select-unit 3 unsigned int rsvd2:1; // 11 reserved unsigned int CAP3EN:1; // 12 Cap. Unit3 enable unsigned int CAP12EN:2; // 13-14 Cap. Unit 1,2 enable unsigned int CAPRES:1; // 15 Capture registers reset }; union CAPCONA_REG { unsigned int all; struct CAPCONA_BITS bit; }; extern volatile union CAPCONA_REG *CAPCONAbits;

95

THE EVENT MANAGERS

// Capture FIFO status register A (CAPFIFOA) struct CAPFIFOA_BITS { unsigned int rsvd1:8; // 0-7 reserved unsigned int CAP1FIFOSTAT:2; // 8-9 CAP1 FIFO status unsigned int CAP2FIFOSTAT:2; // 10-11 CAP2 FIFO status unsigned int CAP3FIFOSTAT:2; // 12-13 CAP3 FIFO status unsigned int rsvd2:2; // 14-15 reserved }; union CAPFIFOA_REG { unsigned int all; struct CAPFIFOA_BITS bit; }; extern volatile union CAPFIFOA_REG *CAPFIFOAbits;

FIFO Status Register A (contains the status of the FIFO registers for Capture Units 1,2,3)

extern volatile unsigned int *T1CNT; //timer1 counter extern volatile unsigned int *T1CMPR; //timer1 compare reg extern volatile unsigned int *T1PR; //timer1 period reg extern volatile unsigned int *CMPR1; // CoMPare Register for Compare unit1 Timer2 Counter, Period and Compare Registers

extern volatile unsigned int *T2CNT;//Timer2 Counter extern volatile unsigned int *T2PR; // Timer2 Period extern volatile unsigned int *T2CMPR;//Timer2 Compare extern extern extern extern extern extern #endif

volatile volatile volatile volatile volatile volatile

unsigned unsigned unsigned unsigned unsigned unsigned

int int int int int int

*CAP1FIFO; *CAP2FIFO; *CAP3FIFO; *CAP1FBOT; *CAP2FBOT; *CAP3FBOT;

/* /* /* /* /* /*

Capture Capture Capture Capture Capture Capture

Channel channel channel channel channel channel

1 2 3 1 2 3

FIFO top */ FIIFO top */ FIFO top */ FIFO BOTtom */ FIFO BOTtom */ FIFO BOTtom */

FIFO Registers for Capture Units 1,2 and 3

Save the changes. You might want to take a second look at all the structures to ensure that you typed it all okay; even a simple misplaced field might cause inexplicable errors or bugs during execution or compilation. Also, try to group the register structures (I tried to keep similar registers grouped together) in a coherent manner. Although having to write a structure for every register in the book is a very tedious work, you will find that there are advantages; the main is that you get to go through the registers first hand, not to mention that you gradually build an extremely useful library. Now, we need to declare all the structures added to “DSP24_Ev.h”. Open the file and add the corresponding declarations shown cased in rectangles in the listing that follows: 96

THE EVENT MANAGERS

#include "DSP24_Ev.h" volatile volatile volatile volatile

unsigned unsigned unsigned unsigned

int int int int

*T1CNT=(void*)0x7401; /* GP timer 1 counter reg */ *T1CMPR=(void*)0x7402; /* GP timer 1 compare reg */ *T1PR=(void*)0x7403; /* GP timer 1 period */ *CMPR1=(void*)0x7417; /* Comp. reg. for Comp. unit1 */ Timer2 Counter, Period and Compare Register

volatile unsigned int *T2CNT=(void*)0x7405; volatile unsigned int *T2PR=(void*)0x7407; volatile unsigned int *T2CMPR=(void*)0x7406; volatile volatile volatile volatile volatile volatile

unsigned unsigned unsigned unsigned unsigned unsigned

int int int int int int

*CAP1FIFO=(void*)0x7423; *CAP2FIFO=(void*)0x7424; *CAP3FIFO=(void*)0x7425; *CAP1FBOT=(void*)0x7427; *CAP2FBOT=(void*)0x7428; *CAP3FBOT=(void*)0x7429;

Capture Unit 1,2 and 3 FIFO Register

//cap1 //cap2 //cap3 //cap1 //cap2 //cap3

FIFO FIFO FIFO FIFO FIFO FIFO

top top top bottom bottom bottom

// GPTCONA volatile union GPTCONA_REG *GPTCONAbits=(void*)0x7400; //T1CON volatile union T1CON_REG *T1CONbits=(void*)0x7404;

Timer2 Control Register

// T2CON volatile union T2CON_REG *T2CONbits=(void*)0x7408; // EVAIMRA volatile union EVAIMRA_REG *EVAIMRAbits=(void*)0x742C; //EVAIMRB volatile union EVAIMRB_REG *EVAIMRBbits=(void*)0x742D;

EVA Interrupt Mask Register B EVA Interrupt Mask Register C

//EVAIMRC volatile union EVAIMRC_REG *EVAIMRCbits=(void*)0x742E; // EVAIFRA volatile union EVAIFRA_REG *EVAIFRAbits=(void*)0x742F; //EVAIFRB volatile union EVAIFRB_REG *EVAIFRBbits=(void*)0x7430; //EVAIFRC volatile union EVAIFRC_REG *EVAIFRCbits=(void*)0x7431;

EVA Interrupt Flag Register B EVA Interrupt Flag Register C

// COMCONA volatile union COMCONA_REG *COMCONAbits=(void*)0x7411; // ACTRA volatile union ACTRA_REG *ACTRAbits=(void*)0x7413; // DBTCONA volatile union DBTCONA_REG *DBTCONAbits=(void*)0x7415;

Capture FIFO status Register A

// CAPFIFOA volatile union CAPFIFOA_REG *CAPFIFOAbits=(void*)0x7422;

97

THE EVENT MANAGERS

// CAPCONA volatile union CAPCONA_REG *CAPCONAbits=(void*)0x7420;

Capture Unit Control Register A

Again, save the changes that you made. Be very careful with the addresses to which the structures are pointing! If the address is not correct, the structure will be pointing to a data memory location that does not correspond to the register! Try to verify the addresses of the registers by doing a cross-check with the TMS320LF/LC240xA DSP Controllers Reference Guide - System and Peripherals. 5.3.3 EVA Interrupt Mask and Flag Registers B and C I assume that by now you are already well acquainted with the fact that every device has its own interrupt mask and flag bit, lying someplace inside a designated interrupt mask and flag register respectively. In the case of timer 2, all interrupt masks (underflow, period match, overflow and compare) can be found in the event manager A interrupt mask register B (EVAIMRB), whereas the corresponding flags can reside in the event manager A interrupt flag register B (EVAIFRB). So far, we have covered the timer 2 events. We still need to acknowledge and handle the capture events, which is exactly the reason we went through all this “trouble” to add new structures. All EVA capture interrupt masks and flags can be found inside the EVA interrupt mask register C (EVAIMRC) and the EVA interrupt flag register C (EVAIFRC). Let us briefly go through each of the two registers. The EVA Interrupt Mask Register B (EVAIMRB). This register (Figure 5-18) contains the interrupt mask bits for timer 2 events (underflow, overflow, period match and compare). Just “unmask” any of those bits by writing a “1” in order to enable the corresponding interrupt.

Figure 5-18. EVA Interrupt Mask Register B11.

The EVA Interrupt Flag Register B (EVAIFRB). In direct correspondence to EVAIMRB, we get an identically mapped register for the interrupt flags of timer 2. Write a “1” to clear the flag and keep in mind that you should always be clearing it inside an interrupt service routine. 11

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

98

THE EVENT MANAGERS

Figure 5-19. EVA Interrupt Flag Register B12.

The EVA Interrupt Mask Register C (EVAIMRC). This mask register contains the interrupt mask bits for capture units 1, 2 and 3. Each unit is associated with one input pin (CAP1, CAP2 and CAP3). You may enable the capture interrupt for each pin/unit by “unmasking” the appropriate bit in EVAIMRC.

Figure 5-20. EVA Interrupt Mask Register C12.

The EVA Interrupt Flag Register C (EVAIFRC). In direct correspondence to EVAIMRC, this register contains the interrupt flags for all 3 capture events in EVA. Write “1” to the corresponding bit to clear the appropriate interrupt flag.

Figure 5-21. EVA Interrupt Flag Register C12.

12

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

99

THE EVENT MANAGERS

5.3.4 Capture Control Register A and Capture FIFO Status Register A Capture units 1, 2 and 3 in EVA can be configured by capture control register A (CAPCONA). Obviously, capture units 4, 5 and 6 are configured by CAPCONB. Both control registers are identically mapped. Figure 5-22 shows the relative control bits for CAPCONA.

Figure 5-22. Capture Control Register A13.

The CAP3EDGE bits. Bits 2 and 3 define the type of edge that the capture unit 3 (CAP3) pin will be detecting. The capture pin can receive the following configurations: 00 : No detection. 01: Rising Edge Detection. 10: Falling Edge Detection. 11: Detects Both Edges. The same values apply for all EDGE configuration bits in CAPCONA. The CAP2EDGE bits. Bits 4 and 5 define the type of edge that capture unit 2 (CAP2) pin will be detecting. The CAP1EDGE bits. Bits 6 and 7 define the type of edge that capture unit 1 pin (CAP1) will be detecting. The CAP3TOADC bit. Bit 8 defines whether capture unit 3 will start the ADC. If this bit is “0”, no action is performed; if “1”, the ADC will start upon a capture event on the CAP3 pin. The CAP12TSEL bit. Bit 9 selects the time base for both capture units 1 and 2. If “1”, the selected time base is timer 1, whereas if “0”, the selected time base is timer 2. Recall that capture units 1 and 2 (as well as 4 and 5) must share the same time base.

13

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

100

THE EVENT MANAGERS

The CAP3TSEL bit. Bit 10 selects the time base for capture unit 3. A value of “1” corresponds to timer 1 and a value of “0” to timer 2. The CAP3EN bit. Bit 12 enables/disables capture unit 3. The CAP12EN bit. Bits 13, 14 enable/disable capture units 1 and 2. Note that you may enable both, or none. Here are the possible configurations: 00 : Both capture units 1 and 2 disabled (FIDO registers retain their values). 01 : Both capture units are enabled. 10 : Reserved. 11 : Reserved. The CAPRES bit. Bit 15 clears the contents of all capture registers in EVA. A “0”-write action clears the registers. Writing a “1” will have no effect. The capture FIFO status register reflects the status of all FIFO registers in EVA. Both capture FIFO status registers A and B are identically mapped. Time instances related to detected pulse edges are stored inside the FIFO registers of the corresponding capture unit. If you are familiar with queue structures, well, this is what these FIFO registers are all about. For each capture unit, we have one capture FIFO top register and one capture FIFO bottom register. For example, capture unit 1 has CAP1FIFO and CAP1FBOT. Assume that CAP1 has detected two falling edges within a small period of time. In that case, CAP1FIFO should contain the value of the timer counter at the very moment that the first edge was detected, whereas CAP1FBOT should contain the value of the timer counter that the second falling edge was detected. In order to track the status of the capture queue, we may read the capture FIFO status register A (CAPFIFOA). Have in mind that this is a twolevel queue (FIFO); therefore, it is advisable to handle the findings fast, otherwise we might miss the time instance of an edge. We will be examining the FIFO capture register operation shortly.

Figure 5-23. Capture FIFO Status Register A14.

14

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

101

THE EVENT MANAGERS

The CAP1FIFO bits. Bits 8-9 reflect the status of capture 1 FIFO registers. A value of “01” indicates that the FIFO contains one entry and should be located at the top of the queue (CAP1FIFO). A value of “00” indicates that the queue is empty. A value of “10” indicates two values in the queue; the newest one is stored at the bottom (CAP1FBOT) and the oldest one at the top (CAP1FIFO). Finally, a value of “11” indicates that a new detection caused the oldest detection entry to be overwritten before being read from the FIFO. The CAP2FIFO bits. Bits 10-11 reflect the status of capture unit 2 FIFO registers. Possible values are identical to the ones for the CAP1FIFO bits. The CAP3FIFO bits. Bits 12-13 describe the status of capture unit 3 FIFO registers. Again, values may be interpreted in the same way as with the CAP1FIFO and CAP2FIFO bits. Note that write actions are permitted on CAPFIFOA and CAPFIFOB. Actually, a “01” – write action to a pair of FIFO status bits (e.g., the CAP1FIFO bits) will cause a capture interrupt related to the corresponding capture unit. It is not generally advisable to perform writeactions to these registers. 5.3.5 Capture FIFO Registers We have well established so far that for each capture unit, there is one capture pin (CAP1-6). For each of those pins, there is a corresponding pair of FIFO registers. This pair of registers can be seen as a size-2 queue, containing time instances of edge detections that occurred on the pin. For pin CAP1, the content of the top of the queue can be read from register CAP1FIFO, whereas the content of the bottom (which is also the second position in the queue) of the queue can be read from register CAP1FBOT. Figure 5-24 illustrates the principle of operation. Although the queue may easily become full (only two positions), the corresponding status register acknowledges that more than two edges have been detected pending a read operation from the top (“11” value in the corresponding status bits). In order to “relief” the queue, you must read the value from the top register (CAP1FIFO). You may also read the bottom register (CAP1FBOT) but it is advisable to “serve” events the way they come; if you do read CAP1FBOT, the corresponding status bits will either become “01” (1 element in queue) if the queue had more than one elements, or “00” (empty queue) if the queue had only one element pending a “read”. It is up to you how to handle the FIFO registers, either inside interrupt service routines, or inside the program main loop. Regardless of approach, you should be always checking with the status register bits and try to read the top register as fast as you can, thereby avoiding a “trail” of edges to “spoil” the small queue.

102

THE EVENT MANAGERS

Figure 5-24. Principle of operation of a Capture Unit FIFO queue.

5.3.6 Initializing Capture Units 1 and 2 Now that we have all the structures required to configure and use the capture units in EVA, we may as usual, write a function that performs initialization of capture units 1 and 2. In fact, we will have to configure all three EVA capture units, since there is only one control register (CAPCONA) for them. Create a new file “DSP24_Cap1.c” and type the following code: #include "DSP24_Ev.h" #include "DSP24_Core.h" void initCap1(void) { // 1. Initializing General Purpose Timer Register A GPTCONAbits->bit.T1PIN=0;// timer1 compare output pin forced low GPTCONAbits->bit.T2PIN=0;// timer2 compare output pin forced low GPTCONAbits->bit.T1TOADC=00; // timer1 does not start ADC GPTCONAbits->bit.T2TOADC=00; // timer2 does not start ADC GPTCONAbits->bit.TCOMPOE=00; // disable compare outputs of timer1 timer2 // 2. Configuring Timer2 Period and initializing counter T2CONbits->all=0x0000; // reset the timer *T2CNT=0; // reseting the Timer2 counter *T2PR=60000; // 3 ms period for /x1 prescaling // 3. Configuring Timer2 Control Register T2CONbits->bit.T1PERSEL=0; // use own period register T2CONbits->bit.TCMPREN=1; // Disable Timer Compare operation T2CONbits->bit.TCLD=00; // reload compare reg on underflow T2CONbits->bit.TMODE=01; // Continuous Up/Down mode T2CONbits->bit.TPS=000; // Input Clock PreScale /x1 T2CONbits->bit.T2SWT1=0; // Do not start with Timer1 T2CONbits->bit.TCLKS=00; // Internal Clock source select T2CONbits->bit.SOFT=0; T2CONbits->bit.FREE=0; // immediate stop on emulation suspend // 4.Configuring CAPture CONtrol Register A (CAPCONA) CAPCONAbits->bit.CAPRES=0; // Clear all capture registers // configuring now CAPCONAbits->bit.CAP3EDGE=00; // No Detection CAPCONAbits->bit.CAP2EDGE=2; // CAP2 Detects falling edge

103

THE EVENT MANAGERS CAPCONAbits->bit.CAP1EDGE=01; // CAP1 Detects rising edge CAPCONAbits->bit.CAP3TOADC=0; // CAP3 does not start ADC CAPCONAbits->bit.CAP12TSEL=0; // Select Timer 2 for CAP1 and 2 CAPCONAbits->bit.CAP3TSEL=0; // Select Timer 2 for CAP3 CAPCONAbits->bit.CAP3EN=0; // Disable CAP3 // 5. Configuring Interrupts EVAIFRCbits->bit.CAP1INT=1; // Clear CAP1 interrupt flag EVAIFRCbits->bit.CAP2INT=1; // Clear CAP2 interrupt flag EVAIMRCbits->bit.CAP1INT=1; // Enable CAP1 interrupt EVAIMRCbits->bit.CAP2INT=1; // Enable CAP2 interrupt *IMR|=0x8; // Unmasking core INT4 // 4. starting timer2 T2CONbits->bit.TENABLE=1; CAPCONAbits->bit.CAP12EN=01; // Enable CAP1 and CAP2 }

The initialization sequence is not very different from the initialization sequence of the compare unit. The earliest priority is to configure the general purpose timer used to clock the capture unit (i.e., timer 2). Consequently, the two first registers to deal with, is GPTCONA and T2CON. The configuration of T2CON is identical in every aspect to the configuration we have already seen for T1CON (timer 1), except for the following bit-field assignment: T2CONbits->bit.T2SWT1=0; // Do not start with Timer1

Recall that timer control registers are identical, but certain bits may have different operations according to the GP Timer involved. The T2SWT1 bit is reserved in T1CON, whereas in T2CON, it enables/disables the synchronized count start with timer 1. In other words, if this bit is set to “1”, timer 2 starts counting whenever timer 1 starts counting. In our case, T2SWT1 is set to “0”; therefore, timer 2 will be started with its own TENABLE bit. The same option is available in T4CON for a synchronized timer 3- timer 4 count start. Now that the timer is configured, the next step is to configure the capture control register. The register configures the type of edge detected on each capture pin. As discussed earlier, for every capture channel you may define a rising edge, falling edge, rising/falling edge detection, as well as no detection. In this example, CAP1 detects rising edges (“01”) and CAP2 detects falling edges (“10”). The CAP3 pin is not going to be used, so a “00” is assigned. The following lines select timer 2 for capture units 1, 2 and 3: CAPCONAbits->bit.CAP12TSEL=0; // Select Timer 2 for CAP1 and 2 CAPCONAbits->bit.CAP3TSEL=0; // Select Timer 2 for CAP3

As mentioned earlier, we have to choose a timer for both capture units 1 and 2. To select timer 2, we assign a value of “0” to the appropriate bit (CAP12TSEL). Accordingly, timer 2 is 104

THE EVENT MANAGERS

selected for capture Unit 3 (CAP3TSEL=0), although the corresponding pin is configured for no detection and the setting will not make any difference. The last step is to enable the interrupts and relative flags for capture unit 1 and 2 (which explains why we added definitions and declarations for EVAIMRC and EVAIFRC). Handling interrupts is obviously important when capturing pulse edges. The capture FIFO must be read as fast as possible in order to release the queue of stored detection times and the best chance of doing it would be upon interrupt service. Before enabling the capture interrupts, better clear the corresponding flags in EVAIFRC: EVAIFRCbits->bit.CAP1INT=1; // Clear CAP1 interrupt flag EVAIFRCbits->bit.CAP2INT=1; // Clear CAP2 interrupt flag

To enable the capture interrupts, simply unmask the relative bits in EVAIMRC: EVAIMRCbits->bit.CAP1INT=1; // Enable CAP1 interrupt EVAIMRCbits->bit.CAP2INT=1; // Enable CAP2 interrupt

We are not done yet with capture interrupts. As you have already seen many times so far, the capture interrupts (much like in the case of any other peripheral) will not work unless we enable the corresponding core interrupt; specifically, INT4 in the case of every capture interrupt (Table 5-1).

Table 5-1. List of Core level INT4 interrupts (Capture 1 and 2 circled)15.

To unmask core INT4, a “1” should be written to bit-3 of core interrupt mask register (IMR): *IMR|=0x8; // Unmasking core INT4

5.3.7 Core INT4 and Capture 1 and 2 Interrupt Service Routines It is time to organize our interrupt service routines. So far, we have been dealing with interrupt sources (timer 1, compare unit 1) that correspond to core level interrupt 2. Since 15

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

105

THE EVENT MANAGERS

capture events correspond to core level interrupt 4, we will have to create a new general interrupt service routine that “intercepts” these interrupts and branches to the appropriate specific interrupt service routines. Open file “DSP24_DefaultISR.c” and add the following code inside function INT4_GISR(): interrupt void INT4_GISR(void){ unsigned int peripheral;

New code

asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM"); switch (peripheral) { case 0x0033: // Capture 1 CAP1INT_ISR(); break; case 0x0034: // Capture 2 CAP2INT_ISR(); break; default: break; } // reset INT4 flag by writing a "1" in bit 3 of IFR *IFR|=8; }

Notice the switch statement branching to functions CAP1INT_ISR() and CAP2INT_ISR(). We will have to add their implementation in “DSP24_DefaultISR.c”. Locate some space after the compare and timer interrupt service routines and type the highlighted code: #include "DSP24_DefaultISR.h" // Timer 1 Period interrupt interrupt void T1PINT_ISR(void) { EVAIFRAbits->bit.T1PINT = 1; // Clear Period Interrupt flag asm(" NOP"); } // Timer 1 Compare interrupt interrupt void T1CINT_ISR(void) { EVAIFRAbits->bit.T1CINT = 1; // Clear Compare Interrupt flag asm(" NOP"); } // Timer 1 Underflow interrupt interrupt void T1UFINT_ISR(void) { EVAIFRAbits->bit.T1UFINT = 1; // Clear UnderFlow Interrupt Flag asm(" NOP"); } // Timer 1 Overflow interrupt

106

THE EVENT MANAGERS interrupt void T1OFINT_ISR(void) { EVAIFRAbits->bit.T1OFINT = 1; // Clear OverFlow Interrupt Flag asm(" NOP"); } // Compare 1 Interrupt interrupt void CMP1INT_ISR(void) { unsigned int test, test2; test=CAPFIFOAbits->bit.CAP1FIFOSTAT; test2=CAPFIFOAbits->bit.CAP2FIFOSTAT; Add Capture 1 Interrupt EVAIFRAbits->bit.CMP1INT=1; // Clear Compare 1 interrupt flag Service Routine asm(" NOP"); } // Capture 1 Interrupt interrupt void CAP1INT_ISR(void) { unsigned int status, top, bottom; status=CAPFIFOAbits->bit.CAP1FIFOSTAT; if (status==1) // one reading in queue top = *CAP1FIFO; else if (status>1) { // 2 or more readings in queue top = *CAP1FIFO; // reading newest detection bottom = *CAP1FIFO; // reading previous detection } EVAIFRCbits->bit.CAP1INT=1; // clear cap1 interrupt flag asm(" NOP"); } // Capture 2 Interrupt interrupt void CAP2INT_ISR(void) { unsigned int status,top, bottom; EVAIFRCbits->bit.CAP2INT=1; // clear cap2 interrupt flag status=CAPFIFOAbits->bit.CAP2FIFOSTAT; if (status==1) // 1 reading in queue top = *CAP2FIFO; else if (status>1) { // 2 or more readings in queue top = *CAP2FIFO; // reading newest detection bottom = *CAP2FIFO; // reading previous detection } asm(" NOP"); }

(CAP1INT_ISR) here.

Add Capture 2 Interrupt Service Routine (CAP2INT_ISR) here.

interrupt void INT1_GISR(void){ } interrupt void INT2_GISR(void) {// GISR for all INT2 level peripherals unsigned int peripheral; asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM"); switch (peripheral) { case 0x0027: // Timer 1 period T1PINT_ISR();

107

THE EVENT MANAGERS break; case 0x0028: // Timer 1 Compare T1CINT_ISR(); break; case 0x0029: // Timer 1 UnderFlow T1UFINT_ISR(); break; case 0x002A: // Timer OverFlow T1OFINT_ISR(); break; case 0x0021: // Compare 1 CMP1INT_ISR(); break; default: break; } // reset the core INT2 flag by writing a "1" in bit 1 *IFR|=2; } interrupt void INT3_GISR(void){ } interrupt void INT4_GISR(void){ unsigned int peripheral; asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM"); switch (peripheral) { case 0x0033: // Capture 1 CAP1INT_ISR(); break; case 0x0034: // Capture 2 CAP2INT_ISR(); break; default: break; } // reset INT4 flag by writing a "1" in bit 3 of IFR *IFR|=8; } interrupt void INT5_GISR(void) { } interrupt void INT6_GISR(void) { }

Save the changes. Let us go through the code inside the ISRs for a bit. Notice variables status, top and bottom declared early in the function. Variable status is used to store the contents of the corresponding FIFO status. status = CAPFIFOAbits->bit.CAP1FIFOSTAT;

If status contains a “01”, then we have only one value pending in the queue, therefore, one read action should clear the queue. If status is “10” (2) or “11” (3), we have two readings 108

THE EVENT MANAGERS

pending in the queue and consequently, we should perform two read actions to clear it. The ifstatement makes sure that, either way, the queue is cleared. if (status==1) // one reading in queue top = *CAP1FIFO; else if (status>1) { // 2 or more readings in queue top = *CAP1FIFO; // reading newest detection bottom = *CAP1FIFO; // reading previous detection }

The code in CAP2INT_ISR() does exactly the same thing for the corresponding registers and status bits (bits CAP2FIFOSTAT and register CAP2FIFO). Notice that all local variables are declared in the first line inside the function. You should always declare local variables in the beginning of a function. Now, open “DSP24_DeafaultISR.h” and add the highlighted declarations for CAP1INT_ISR() and CAP2INT_ISR() shown below: #ifndef DSP24_DEFAULT_ISR_H #define DSP24_DEFAULT_ISR_H #include "DSP24_Ev.h" #include "DSP24_Core.h" // Timer 1 Period Match interrupt interrupt void T1PINT_ISR(void); // Timer 1 Compare interrupt interrupt void T1CINT_ISR(void); // Timer 1 Underflow interrupt interrupt void T1UFINT_ISR(void); // Timer 1 Overflow interrupt interrupt void T1OFINT_ISR(void);

Add here external declarations for CAP1INT_ISR() and CAP2INT_ISR().

// CAPture 1 interrupt interrupt void CAP1INT_ISR(void); //CAPture 2 interrupt interrupt void CAP2INT_ISR(void); // Compare 1 Interrupt interrupt void CMP1INT_ISR(void); // Core Level General ISRs interrupt void INT1_GISR(void); interrupt void INT2_GISR(void); interrupt void INT3_GISR(void); interrupt void INT4_GISR(void); interrupt void INT5_GISR(void);

109

THE EVENT MANAGERS

interrupt void INT6_GISR(void); #endif

Save the file, we are almost done! 5.3.8 Configuring Capture I/O Pins 1 and 2 As always, we should configure the operation of the I/O pins used in the program. If you take a look into the TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Chapter 5), you will find that CAP1, 2 and 3 pins are multiplexed with port A general I/O pins 2, 3 and 4 (Table 5-2). You may configure these pins with MCRA. To do this, we need to modify the initIO() function in file “DSP24_Sys.c” to configure port A pins 2,3 and 4 for capturing operation (CAP3 is also configured for its primary function, although not used in the program).

Table 5-2. Port A and B I/O pins configured by MCRA (CAP1 and CAP2 circled)16.

Open “DSP24_Sys.c” and replace the previous code in initIO() with the following: void initIO(void) { // setting up CAP1,2 and 3 for edge detection operation MCRAbits->bit.CAP1QEP1_GPIOA3=1; MCRAbits->bit.CAP2QEP2_GPIOA4=1; 16

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

110

THE EVENT MANAGERS MCRAbits->bit.CAP3_GPIOA5=1; }

Save the file. We are now ready to compile and download. 5.3.9 Executing the Program Add all files to project “CapUnit” in case you haven’t done that already and rebuild. Your project browser window should look like the one in Figure 5-25. If all goes well, download your program to the DSP, do a Reset CPU and then, Go Main. Before you hit Run, add two breakpoints inside the interrupt service routines CAP1INT_ISR() and CAP2INT_ISR() as shown in Figure 5-26.

Figure 5-25. Project Browser Window view for “CapUnit”.

111

THE EVENT MANAGERS

Figure 5-26. Breakpoints added inside CAP1 and CAP2 ISRs.

Now, the only thing left to consider is how to test the capture pins. Presumably, you do not have a motor encoder along with a spinning motor; a good solution would be to simply connect the capture pins to a +3.3V or ground reference pin on the DSP and manually trigger edge detection just once or twice. Then, you can take your time to examine the capture FIFO registers and status bits while execution is suspended inside one of the capture ISRs.

Figure 5-27. Locations of CAP1, CAP2 and Ground on the I/O header17.

17

eZdsp™ LF2407A– Technical Reference (Spectrum Digital)

112

THE EVENT MANAGERS

5.4 Conclusions/Suggestions The event managers are enormous peripherals, not only in terms of configuration, but of significance as well. Along with the controller area network (CAN) controller, they are the most extensive peripherals on the 2407A. On the other hand, they provide a wide variety of tools to create and handle signals in the most efficient way, so it is important to get acquainted as much as you can with them. The versatility and accuracy provided by these peripherals are no match for the corresponding software-oriented solutions. The following chapters deal with much less extensive peripherals (ADC, SCI, SPI) with a significantly limited number of registers compared to EVA and EVB. Before you move on, you should complete the list of structures with all remaining registers (the ones we did not use in the examples) involved in both event managers and complete files “DSP24_Ev.h” and “DSP24_Ev.c” using the TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals. Sooner or later you will have to use those registers, so this is a good time to complete the definitions and declarations.

113

THE EVENT MANAGERS

114

THE ANALOG TO DIGITAL CONVERTER

6

The Analog to Digital Converter

6.1 Overview of the Analog to Digital Converter The analog to digital converter (ADC) along with EVA and EVB are, arguably, the most important peripherals on all C2000 platform DSPs by Texas Instruments. The ADC of the 2407A is a highly configurable 10-bit ADC with built-in sample-and-hold, two sequencers and 16 buffer registers to store the sampled results. The ADC can be triggered by the event managers, software, or externally, with the ADC start-of-conversion (ADCSOC) pin. Configuration of the ADC is rather extensive and we will get the opportunity to better acquaint ourselves with all features of the peripheral throughout examples, rather than just enumerating them in this section. However, it is important to gain a general insight into the principles of ADC operation in the LF2407A. 6.1.1 The Sequencers The ADC can sample 16 different channels, physically corresponding to pins ADC0ADC15 (non-multiplexed) located on a separate pin header on the DSP board. A conversion run (session) involves a number of up to 16 samples, referred to as “auto-conversions”, from some, or all of the ADC0-ADC15 pins, according to configuration. An auto-conversion sequence is synchronized by two sequencers (SEQ1 and SEQ2) which can be thought of, as finite state machines that arbitrate a maximum number of eight auto-conversions as a chain of consecutive state changes. Although you do not need to be aware of the hardware specifics of each sequencer, nevertheless, you need to be acquainted with the concepts of their operation, since certain events related to their operation are “tied” to the ADC interrupt. The sequencers can either operate in dual or cascaded mode. In dual mode, each sequencer performs a number of sampling steps (auto-conversions) independently of the other; you may think of it as having two distinct ADCs working as totally independent entities. In cascaded mode, sequencers are “joined” to form a single 16-state sequencer and therefore, performing a number of auto-conversions as a single ADC. Apparently, depending on whether we operate the ADC in dual sequencer mode (therefore, we have two pseudo-ADCs sampling a maximum of 8 channels each) or cascaded mode (unique ADC sampling a maximum of 16 channels) the way we handle the ADC interrupts should be distinctively different. Figures 6-1 and 6-2 show the block diagrams of the ADC in dual and cascaded sequencer mode respectively. 115

THE ANALOG TO DIGITAL CONVERTER

Figure 6-1. ADC Block Diagram with Dual Sequencers1.

Obviously, in dual Sequencer mode, the two sequencers are sharing the very same ADC; still, their utilization in our program would be as if we are managing two distinct ADCs performing a number of auto-conversions per conversion step. Accordingly, we may define different triggering sources for start of conversion (SOC) in each case (e.g., SEQ1 SOC triggered by EVA, while SEQ2 SOC triggered by software). In the same way, the respective conclusions of 1

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

116

THE ANALOG TO DIGITAL CONVERTER

conversion runs are bound to be asynchronous to each other; this means that we will have to acknowledge and handle them separately in the ADC interrupt service routine.

Figure 6-2. ADC Block Diagram with cascaded sequencers (as a single 16-state sequencer)2.

Things are more straightforward in cascaded mode: The ADC can perform a maximum number of 16 auto-conversions in a single conversion run; SOC can be triggered by only one source and the ADC ISR should be handling all the samples at once, following a conversion run.

6.2 Using the ADC in Cascaded Sequencer Mode An example can take us through all the necessary steps to configure and operate the ADC in cascaded sequencer mode. Create a folder “ADC-CS” and copy files “DSP24_Core.h”, “DSP24_Core.c”, “DSP24_Gpio.h”, “DSP24_Gpio.c”, “DSP24_Sys.c”, “2407_cmd.cmd”, “DSP24_DefaultISR.h”, “DSP24_DefaultISR.c” and “cvectors.asm” into it. Now, create a new project named “ADC-CS” and add the files to it. Lastly, don’t forget to add file “rts2xx.lib” to 2

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

117

THE ANALOG TO DIGITAL CONVERTER

the project. As a next step, we need to create a header and a C file with the necessary register structure definitions and declarations for the ADC registers. 6.2.1 Creating header and C files for the ADC Registers The ADC does not involve a large number of registers, therefore the related header and C files are not very extensive. Create a new file named “DSP24_Adc.h” and type the following: ADC Control Register 1 structure definition

#ifndef DSP24_ADC_H #define DSP24_ADC_H //ADCTRL1 struct ADCTRL1_BITS unsigned int unsigned int unsigned int unsigned int unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned

int int int int int int int int int

{ STESTENA:1; // 0 Self TEST mode enabled HILO:1; // 1 Test Voltage for Test Mode BRGENA:1; // 2 Allows a refernce voltage in calibration CALENA:1; // 3 Calibration enable - disabled the // input channel multiplexer to calibrate SEQCASC:1; // 4 SEQuencer CASCaded operation INTPRI:1; // 5 ADC interrupt request priority CONTRUN:1; // 6 Continuous run CPS:1; // 7 Conversion Clock Prescale ACQPS:4; // 8-11 acquisition windows prescale FREE:1; // 12-13 configure SOFT:1; // operation on emulation suspend RESET:1; // 14 ADC software reset rsvd1:1; // 15 reserved

}; union ADCTRL1_REG { unsigned int all; struct ADCTRL1_BITS bit; }; extern volatile union ADCTRL1_REG* ADCTRL1bits; //ADCTRL2 struct ADCTRL2_BITS unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int };

ADC Control Register 2 structure definition

{ EVBSOCSEQ2:1; // 0 Allows SOC trigger by EVB INTFLAGSEQ2:1; // 1 INTerrupt flag for SEQ2 INTENASEQ2:2; // 3-2 INTerrupt mode ENAble for SEQ2 SEQ2BSY:1; // 4 SEQ2 BuSY flag SOCSEQ2:1; // 5 SOC trigger for SEQ2 RSTSEQ2:1; // 6 ReSeT SEQ2 EXTSOCSEQ1:1; // 7 Allows SOC by the ADSOC pin EVASOCSEQ1:1; // 8 Allows SOC by EVA INTFLAGSEQ1:1; // 9 INTerrupt Flag for SEQ1 INTENASEQ1:2; // 10-11 Interrupt mode for SEQ1 SEQ1BSY:1; // 12 Sequencer1 busy SOCSEQ1:1; // 13 SOC triogger for SEQ1 RSTSEQ1STRTCAL:1; // 14 resets sequencer to CONV00 state EVBSOCSEQ:1; // 15 SOC by EVB (cascaded mode)

118

THE ANALOG TO DIGITAL CONVERTER

union ADCTRL2_REG { unsigned int all; struct ADCTRL2_BITS };

bit;

Autosequence Status Register structure definition

extern volatile union ADCTRL2_REG *ADCTRL2bits; // AUTO_SEQ_SR struct AUTO_SEQ_SR_BITS { unsigned int SEQ1STATE:3; // 0-3 SEQ1 state (only in dual mode) unsigned int SEQ2STATE:2; //4-6 SEQ2 state (only in dual mode) unsigned int rsvd2:1; unsigned int SEQCNTR:4; // 8-11 Sequencing counter status bits unsigned int rsvd1:4; //12-15 reserved }; union AUTO_SEQ_SR_REG { unsigned int all; struct AUTO_SEQ_SR_BITS bit; }; extern volatile union AUTO_SEQ_SR_REG *AUTO_SEQ_SRbits; // CHSELSEQ1 struct CHSELSEQ1_BITS { unsigned int CONV00:4; unsigned int CONV01:4; unsigned int CONV02:4; unsigned int CONV03:4; }; union CHSELSEQ1_REG{ unsigned int all; struct CHSELSEQ1_BITS bit; }; extern volatile union CHSELSEQ1_REG *CHSELSEQ1bits; //CHSELSEQ2 struct CHSELSEQ2_BITS { unsigned int CONV04:4; unsigned int CONV05:4; unsigned int CONV06:4; unsigned int CONV07:4; }; union CHSELSEQ2_REG{ unsigned int all; struct CHSELSEQ2_BITS };

bit;

extern volatile union CHSELSEQ2_REG *CHSELSEQ2bits;

119

THE ANALOG TO DIGITAL CONVERTER //CHSELSEQ3 struct CHSELSEQ3_BITS { unsigned int CONV08:4; unsigned int CONV09:4; unsigned int CONV10:4; unsigned int CONV11:4; }; union CHSELSEQ3_REG{ unsigned int all; struct CHSELSEQ3_BITS bit; }; extern volatile union CHSELSEQ3_REG *CHSELSEQ3bits; //CHSELSEQ4 struct CHSELSEQ4_BITS { unsigned int CONV12:4; unsigned int CONV13:4; unsigned int CONV14:4; unsigned int CONV15:4; }; union CHSELSEQ4_REG { unsigned int all; struct CHSELSEQ4_BITS bit; }; extern volatile union CHSELSEQ4_REG *CHSELSEQ4bits; // MAXCONV extern volatile unsigned int *MAXCONV; // RESULT REGISTERS (access i-th register as: *(RESULT+i), i=0,...,15) extern volatile unsigned int *RESULT; // CALIBRATION REGISTER extern volatile unsigned int *ADCCAL; #endif

Save the file and create the corresponding C file, “DSP24_Adc.c”. Type the following: #include "DSP24_Adc.h" volatile union ADCTRL1_REG *ADCTRL1bits = (void*)0x70A0; volatile union ADCTRL2_REG *ADCTRL2bits = (void*)0x70A1; volatile union AUTO_SEQ_SR_REG *AUTO_SEQ_SRbits = (void*)0x70A7; volatile union CHSELSEQ1_REG *CHSELSEQ1bits = (void*)0x70A3; volatile union CHSELSEQ2_REG *CHSELSEQ2bits = (void*)0x70A4;

120

THE ANALOG TO DIGITAL CONVERTER

volatile union CHSELSEQ3_REG *CHSELSEQ3bits = (void*)0x70A5; volatile union CHSELSEQ4_REG *CHSELSEQ4bits = (void*)0x70A6; // Single word pointers volatile unsigned int *MAXCONV = (void*)0x70A2; volatile unsigned int *RESULT = (void*)0x70A8; volatile unsigned int *ADCCAL = (void*)0x70B8;

Save file “DSP24_Adc.c” and add it to the project. Scan the project for dependencies and the header file (“DSP24_Adc.h”) should appear under the Include branch on your project view window. We may now take a look at the registers and their corresponding fields that we have just included in our project. 6.2.2 The ADC Control Register 1 (ADCTRL1) The ADC is configured with two control registers, ADC control register 1 and (ADCTRL1) and ADC control register 2 (ADCTRL2). ADCTRL1 contains mainly configuration settings regarding calibration and normal operation (Figure 6-3).

Figure 6-3. The ADC Control Register 13.

The STEST ENA bit. Bit 0 in ADCTRL1 (STESTENA) enables the self-test function (“1”enabled, “0”-disabled). The HI/LO bit. Bit 1 is used to define the type of voltage currently being calibrated or tested. Specifically, in self-test mode, the bit “informs” the ADC about whether we are testing the low voltage reference (“0”) or the high voltage reference (“1”). In calibration mode, with respect to the value of the BRG ENA bit, the bit “informs” the ADC about whether we are calibrating , or according to Table 6-1. 3

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

121

THE ANALOG TO DIGITAL CONVERTER

BRG ENA

HI/LO

CALIBRATION VOLTAGE

0

0

VREFLO

0

1

VREFHI

1

0

1

1

Table 6-1. Reference voltage in calibration mode for the various values of BRGENA and HILO bits.

The BRG ENA bit. Bit 2 is the bridge enable bit. When set, it allows mid-point voltages to be calibrated according to Table 6-1. The CAL ENA bit. Bit 3 is enables calibration mode (“1”). If calibration is disabled (“0”), the BRG ENA bit is ineffective. The SEQ CASC bit. Bit 4 enables/disables cascaded sequencer mode. If “0”, the ADC operates in dual sequencer mode; if “1”, ADC operates in cascaded sequencer mode. The INT PRI bit. Bit 5 configures the ADC interrupt priority. A value of “0” corresponds to high priority and “1” to low priority interrupts requests. The CONT RUN bit. Bit 6 configures the sequencer’s action upon the end of an autoconversion sequence. As mentioned earlier, sequencers are finite state machines with states corresponding to sampling steps. Either operating in cascaded or dual sequencer mode, the sequencer(s) may stop and remain in the last state (CONT RUN = 0) or reset and start the sequence all over again (CONT RUN = 1). In most cases we will be using continuous run (CONT RUN = 1); however, according to the application’s needs, we may require a sequencer to remain idle after just one single auto-conversion sequence (ADC conversion step) and reserve the right to reset (and possibly, restart) at a later time. The CPS bit. Bit 7 configures the conversion clock prescaling. If “0”, FCLK = CLK, whereas if “1”, FCLK = CLK/2. The ADC uses a clocking signal which may be equal to or half the period of the CPU clock. The time during which a single sample is taken (also referred to as acquisition time window) is configured by the ACQ PS1, ACQ PS2, ACQ PS3 and AC PS4 bits in terms of this particular clocking signal (F CLK). The ACQ PS1, ACQ PS2, ACQ PS3, ACQ PS4 bits. Bits 8-11 configure the acquisition time window. The acquisition time window, also referred to as Sample and Hold (S/H) window, is a period immediately prior to a sampling action. The length of the acquisition window is directly related to the impedance of the ADCIN0-15 pins (see Tables 7-3 and 7-4 in 122

THE ANALOG TO DIGITAL CONVERTER

the TMS320LF/LC240xA Controllers Reference Guide – System and Peripherals); therefore, by adjusting the ACQPS bits, we may adjust the impedance of the ADC. The length of an acquisition window can be calculated by . The overall length of an auto-conversion should be equal to the S/H window length, added to the sampling time (TSAMPLE = 11TCLK ):

where TCLK is the period of the ADC clock signal. It is important to think of the ACQPS bits as a way of configuring the impedance of the ADC, rather than a time delay during an autoconversion. We will be putting the above calculations to a test using an oscilloscope later on in our example. The SOFT and FREE bits. Bits 12 (FREE) and 13 (SOFT) configure the operation of the ADC upon emulation suspension. Much like in the cases of most peripherals seen so far, possible settings are: a) SOFT-FREE=00 for immediate stop on emulation suspend, b) SOFTFREE = 10 for completing current task before stopping and c) SOFT-FREE=X1 for free run. The RESET bit. Bit 13 resets the ADC sequencers with a “1”-write action. Writing “0” causes no action. 6.2.3 The ADC Control Register 2 (ADCTRL2) The ADC Control Register 2 (ADCTRL2) configures the sequencer 1 and 2 interrupts and contains the relative flags. The ADCTRL2 also contains SOC triggers for the sequencers in any operation mode (dual or cascaded).

Figure 6-4. The ADC Control Register 2 (ADCTRL2)4.

4

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

123

THE ANALOG TO DIGITAL CONVERTER

The EVB SOC SEQ2 bit. Bit 0 is a mask for sequencer 2 SOC triggers from EVB. Specifically, if the bit is “0”, sequencer 2 SOC triggers from EVB will be ignored. The INT FLAG SEQ2 bit. Bit 1 is the interrupt flag for sequencer 2. The flag is raised once upon completion of the last auto-conversion by sequencer 2 in dual mode. The flag should be cleared manually with a “1”-write action. The INT ENA SEQ 2 bits. Bits 2-3 configure and/or enable the interrupt request for SEQ2 as shown in Table 6-2. Bit 3

Bit 2

OPERATION DESCRIPTION

0

0

Interrupt is disabled

0

1

Interrupt request upon SEQ2 flag set (Mode 1)

1

0

Interrupt request ONLY if SEQ2 is already set (Mode 2)

1

1

Reserved

Table 6-2. Interrupt enable modes for sequencer 2

In mode 1, an interrupt is triggered every time the sequencer has finished the last sampling run (i.e., reached the end of conversion). In mode 2, upon EOC, the interrupt will not be triggered, unless the SEQ2 INT flag is already set; practically, this means that the sequencer will cause an interrupt every second EOC. The SEQ2 BSY. Bit 4 is a flag indicating whether an auto-conversion sequence is in progress (“1”) or not (“0”). The SOC SEQ2, Bit 5 initiates a sequencer 2 SOC. If we write a “1” to this bit, the sequencer will start and the bit will be automatically cleared. If the sequencer was already in the middle of a conversion run and the bit was cleared, writing a “1” will result in restarting the sequencer after the end of the current conversion and the bit being cleared upon restart. If we attempt to write a “1” while the bit is still “1” and the sequencer in operation, the action will be ignored. Clearing the bit (“0” over “1”) causes a pending SOC to be aborted. In practice, you may use this bit to start sequencer 2 in your programs by setting it to “1”; just remember that the bit is cleared shortly after the sequencer has started the auto-conversion sequence. The RST SEQ2 bit. Bit 6 resets the sequencer with a “1”-write action and aborts the current auto-conversion sequence. Writing a “0” to this bit causes no action.

124

THE ANALOG TO DIGITAL CONVERTER

The EXT SOC SEQ1. Bit 7 is a mask bit for external pin (ADCSOC) SOC triggers for sequencer 1. If “0”, sequencer 1 cannot be started by the ADCSOC pin. The EVA SOC SEQ1. Bit 8 is a mask bit for EVA SOC triggers for sequencer 1. If “0”, EVA cannot start sequencer 1. The INT FLAG SEQ1 bit. Bit 9 is the interrupt flag for sequencer 1. It must be manually cleared with a “1”-write action. This flag is also used to indicate an end of conversion for the 16-state sequencer in cascaded sequencer mode. The INT ENA SEQ1 bits. Bits 10-11 configure and/or enable sequencer 1 interrupt requests. The possible configurations are identical to the ones for INT ENA SEQ2 (Table 6-2). The SOC SEQ1 bit. Bit 12 starts sequencer 1. The operation of SOC SEQ1 is identical to the operation of SOC SEQ2. The RST SEQ1 / STRT CAL bit. Bit 13 can either be used to reset sequencer 1, or start calibration. Writing a “0” causes no action; however, if calibration is enabled (CALENA = 1 in ADCTRL1), writing a “1” will start the calibration, whereas if calibration is disabled, writing a “1” will reset sequencer 1. The EVB SOC SEQ bit. Bit 15 is a mask bit for EVB SOC triggers for the cascaded sequencer; If “0”, EVB cannot start the cascaded sequencer. 6.2.4 Autosequence Status Register (AUTO_SEQ_SR) The autosequence status register is a read-only register containing information and flags regarding the state of each sequencer.

Figure 6-5. The Autosequence Status Register (AUTO_SEQ_SR)5.

5

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

125

THE ANALOG TO DIGITAL CONVERTER

The SEQ1-State0 - SEQ1-State3 bits. Bits 0-3 contain a number reflecting the current state of sequencer 1. Obviously, the state number can tell us how many auto-conversions have been completed so far and consequently, we may read those results prior to an interrupt. In cascaded mode, these bits reflect the state of the cascaded sequencer. The SEQ2-State0 – SEQ2-State2 bits. Bits 4-6 reflect the state of sequencer 2 in a conversion step. In cascaded mode, the bits are not relevant to the cascaded sequencer’s state and should be ignored. The SEQ CNTR0 – SEQ CNTR3 bits. Bits 8-11 contain the number of remaining autoconversions in a sequence, initially equal to a preset number stored in the Maximum Conversion Channels Register (MAXCONV). In cascaded mode, these bits specifically refer to the cascaded sequencer’s remaining conversions. However, in dual sequencer mode, these bits can either refer to remaining auto-conversions of sequencer 1 or to the remaining conversions of sequencer 2! Recall that in dual sequencer mode, SEQ1 and SEQ2 “share” the ADC by taking turns; therefore, with respect to which of the two sequencers is currently “busy” (flags SEQ1BSY and SEQ2BSY in ADCTRL2), the SEQ CNTR0-3 bits will reflect the remaining conversions of that particular sequencer. In practice, this is yet another way of obtaining information regarding the state of a sequencer. 6.2.5 The Maximum Conversion Channels Register (MAXCONV) The maximum conversion channels register (MAXCONV) configures the number of auto-conversions occurring in a conversion step of sequencer 1, sequencer 2, or the cascaded sequencer.

Figure 6-6. The Maximum Conversion Channels Register (MAXCONV) 6.

The MAXCONV1_0 – MAXCONV1_3 bits. Bits 0-3 configure the number of autoconversions in a conversion session for the cascaded sequencer. In dual sequencer mode, only

6

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

126

THE ANALOG TO DIGITAL CONVERTER

bits 0-2 (MAXCONV1_0 – MAXCONV1_2) are used to configure the number of autoconversions in a conversion session for sequencer 1. The MAXCONV2_0 – MAXCONV2_2 bits. Bits 4-6 configure the number of autoconversions in a conversion session for sequencer 2. 6.2.6 The ADC Input Channel Select Sequencing Control Registers (CHSELSEQ n) An auto-conversion sequence can be configured to sample a subset or the entire set of available 16 ADC channels (ADCIN0, …, ADCIN15). It is totally up to the programmer’s discretion to configure how many and which channels will be polled in a conversion session. As an example, imagine that you wish to poll two analog sensors with the cascaded sequencer: Assume we connect the first sensor to ADCIN0 and the second to ADCIN1. In this case, we should set the first three bits of MAXCONV (MAXCONV1_0 – MAXCONV1_3) equal to 2 for a number of two conversions in the cascaded sequencer’s conversion session; moreover, we should “tell” the ADC which channels (ADCIN0 and ADC1 in this case) to sample in the first and second auto-conversions. To achieve this, we must configure the appropriate channel select registers.

Figure 6-7. Input Channel Select Sequencing Control Registers 1-4 (CHSELSE1-4)7.

7

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

127

THE ANALOG TO DIGITAL CONVERTER

Registers CHSELSEQ1-2 define which channels are sampled in a conversion session by sequencer 1, whereas CHSELSEQ3-4 define the sampled channels in a conversion session by sequencer2. In cascaded sequencer mode, all four registers configure the sampled channels in the unified sequencer’s conversion session. Each channel in CHSELSEQn corresponds to a 4-bit entry (ADCIN0, ADCIN1, …, ADCIN15). 6.2.7 ADC Conversion Result Buffer Registers (RESULT n) The conversion results in an auto-conversion run are stored in 16 result registers which can be accessed in consecutive memory locations, starting from address 70A8h (RESULT0).

Figure 6-8. Conversion Result Buffer Registers (RESULTn)8.

Result registers are read-only and you should be aware that the 10-bit long result of the conversion is stored in the 10 highest bits of the register; therefore, in order to obtain the actual value of the conversion result, you will have to copy the contents of the register in a variable and thereafter, perform right-shifting by 6 positions (or divide by 64). 6.2.8 Initializing the ADC in Cascaded Sequencer Mode We now need to write some code that configures the ADC in cascaded sequencer mode. We will be using a software trigger to start the sequencer and thereafter “lock” it in continuous run. Clock prescaling is /x1 (CPS = 0) and the ACQ bits will be set to 9 (10T CLK). A conversion session will be configured to sample all 16 channels (ADCIN0-ADCIN15). Create a new C file named “ADCFuncCS.c” and type the following: #include "DSP24_Core.h" #include "DSP24_Adc.h" void initADC_CS(void) {

8

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

128

THE ANALOG TO DIGITAL CONVERTER // 1. Configuring ADCTRL1 ADCTRL1bits->bit.STESTENA = 0; // self-test mode disabled ADCTRL1bits->bit.HILO = 0; // no effect in normal mode ADCTRL1bits->bit.BRGENA = 0; // no effect in normal mode ADCTRL1bits->bit.CALENA = 0; // calibration mode disabled ADCTRL1bits->bit.SEQCASC = 1; // use cascaded sequencer mode ADCTRL1bits->bit.INTPRI = 0; // high priority ADC interrupt ADCTRL1bits->bit.CONTRUN = 1; // Continuous conversion mode ADCTRL1bits->bit.CPS = 0; // Conversion ClOCK = CLKOUT/1 ADCTRL1bits->bit.ACQPS = 9; // Acquisition time = 10*Tclk=10*Clkout ADCTRL1bits->bit.SOFT = 0; ADCTRL1bits->bit.FREE = 0; // immediate stop on suspend ADCTRL1bits->bit.RESET = 0; // no action for now // 2. Configuring ADCTRL2 ADCTRL2bits->bit.EVBSOCSEQ2 = 0; // EVB cannot start sequencer2 ADCTRL2bits->bit.INTFLAGSEQ2 = 1; // clear SEQ2 interrupt flag ADCTRL2bits->bit.INTENASEQ2 = 0; // disable SEQ2 interrupt ADCTRL2bits->bit.SOCSEQ2 = 0; // ignored in cascaded mode ADCTRL2bits->bit.RSTSEQ2 = 0; // no action ADCTRL2bits->bit.EXTSOCSEQ1 = 0; // no action ADCTRL2bits->bit.EVASOCSEQ1 = 0; // EVA cannot trigger SOC on SEQ1 ADCTRL2bits->bit.INTFLAGSEQ1 = 1; // clear interrupt SEQ1 flag ADCTRL2bits->bit.INTENASEQ1 = 1; // enable SEQ1 interrupt ADCTRL2bits->bit.SOCSEQ1 = 0; // no action (clear pending SOC triggers) ADCTRL2bits->bit.RSTSEQ1STRTCAL = 0; // no action ADCTRL2bits->bit.EVBSOCSEQ = 0; // EVB does not start cascaded sequencer // 3. MAXimum CONversion number (MAXCONv) *MAXCONV = 0x000F; // 16 conversions // 54. Input Channel Select CHSELSEQ1bits->bit.CONV00 = CHSELSEQ1bits->bit.CONV01 = CHSELSEQ1bits->bit.CONV02 = CHSELSEQ1bits->bit.CONV03 =

Sequencing 0; 1; 2; 3;

CHSELSEQ2bits->bit.CONV04 CHSELSEQ2bits->bit.CONV05 CHSELSEQ2bits->bit.CONV06 CHSELSEQ2bits->bit.CONV07

= = = =

4; 5; 6; 7;

CHSELSEQ3bits->bit.CONV08 CHSELSEQ3bits->bit.CONV09 CHSELSEQ3bits->bit.CONV10 CHSELSEQ3bits->bit.CONV11

= = = =

8; 9; 10; 11;

CHSELSEQ4bits->bit.CONV12 = 12; CHSELSEQ4bits->bit.CONV13 = 13; CHSELSEQ4bits->bit.CONV14 = 14; CHSELSEQ4bits->bit.CONV15 = 15; // 6. enable interrupts *IMR |= 0x0001; // enable int1 core interrupt (ADC HIGH PRIORITY) // 7. Start the ADC (Software trigger) ADCTRL2bits->bit.SOCSEQ1 = 1; }

129

THE ANALOG TO DIGITAL CONVERTER

The early configuration steps should involve ADC control registers 1 and 2. In ADCTRL1, we disable self-test (STESTENA = 0) and calibration (CALENA = 0); with these settings the HILO and BRGENA are ignored (nevertheless, set to “0” as well). To enable cascaded sequencer mode, the SEQCASC bit is set to “1”. For high priority interrupt request, INTPRI is “0”. Also, notice that CONTRUN bit is “1”, meaning that the sequencer will reset after the end of conversion and restart a new conversion session without any triggering required; in other words the sequencer should be restarting the conversion session indefinitely. Notice that CPS is “0” and ACQPS bits have a value of 9: This means that the ADC clock has the same period with the CPU clock (40 Mhz), whereas the acquisition window should have a length of , (where TCLK is the period of the ADC clock); keep these settings in mind, as we will be attempting to verify the total time required for a conversion session of the cascaded sequencer using an oscilloscope, later in this example. The FREE and SOFT bits are set to “00” for immediate ADC stop on suspend (as in most examples in this text). Finally, although this step could be skipped, we write a “0” to the RESET bit, which causes no action. In ADCTRL2, since the sequencer will be started by software, it would be nice if we disabled EVA, EVB and external pin (ADCSOC) trigger masks (EVBSOCSEQ2 = 0, EVASOCSEQ1, EVBSOCSEQ = 0 and EXTSOCSEQ1 = 0), just to ensure that a timer in EVA/EVB or voltage on the ADCSOC pin will not accidentally cause a SOC on any of the sequencers. In fact, EVA/EVB triggering is a likely event to occur, if you are using a timer and you have “forgotten” to set the TxTOADC bit to “0” in its configuration. As a second step, make sure you’ve cleared all interrupt flags for sequencer 1 and 2 (write “1” to INTFLAGSEQ1 and INTFLAGSEQ2); note that only the interrupt flag of sequencer 1 is of interest in cascaded mode, however it would be nice to clear both flags. Lastly, for demonstration purposes, we write a “0” to reset bits RSTSEQ2 and RSTSEQ1STRTCAL (no action). Since we require the ADC to poll all 16 ADC channels (ADC0 – ADC15) in a conversion session, the first four bits of MAXCONV are loaded with a value of 15 (0Fh). The remaining bits are zero (no effect); therefore, the entire register is also loaded with 15. In the last step we need to configure which of the ADC channels are going to be sampled in each one of the 16 auto-conversion steps. For this, we load CONV00 with 0, CONV01 with 1, …, CONV15 with 15 in the corresponding CHSELSEQ1-4 registers. In practice, most applications should deal with much fewer analog devices; therefore a more reasonable value for MAXCONV would 1 or 2, and the corresponding channels, CONV00 and CONV01, could be any two of the available sixteen channels. In other occasions, you may wish to “oversample” a channel by sampling it multiple times in a conversion run (e.g., setting CONV00 = channel, CONV01 = channel, CONV03 = channel, …).

130

THE ANALOG TO DIGITAL CONVERTER

Lastly, we enable core interrupt INT1 in IMR and start the cascaded sequencer by writing a “1” in the SOCSEQ2 bit in ADCTRL2. 6.2.9 The ADC High Priority Interrupt Service Routine It’s time to write our interrupt service routine. Open file “DSP24_ISR.c” and add a new function as follows: interrupt void ADCINTHP_ISR() { unsigned int results[16]; int i; // copying results for (i=0; ibit.INTFLAGSEQ1 = 1; // toggling general I/O pin 0 in port A PADATDIRbits->bit.IOPA0 = (PADATDIRbits->bit.IOPA0 == 0) ? 1 : 0; asm(" NOP"); }

The ISR code initially copies the 16 results from the result registers into an array of 16-bit unsigned integers. We have declared only one pointer (RESULT) to the memory location of the first result in the ADC header and corresponding C file; since result registers can be accessed by consecutive memory locations, RESULT+1 should be pointing to the second result register, RESULT+2 to the third and so on. Also notice the division by 64: Recall that the actual result is stored in the highest 10 bits of the register, therefore we need to shiftright the data by 6 positions in order to get the actual value; hence, the division by 64 = 26. In the end of the ISR, the output of I/O pin A0 is toggled; we will be using this pin to measure the time interval between two consecutive ADC interrupts. Note here that you can shift the result register using the right-shift C operator (>>) instead of dividing by 64; if you do so, you should clear the sign extension flag with inline assembly instruction, asm(“ CLRC SXM”): asm(" CLRC SXM"); // copying results for (i=0; i> 6; asm(" SETC SXM"); // restore SXM according to the needs of the application

The sign-extension mode (SXM) bit is a control bit in the CPU status register, determining whether the shifter will add sign extension bits to the result of a shift-operation. To restore the sign-extension bit after reading a result, use asm(“ SETC SXM”). 131

THE ANALOG TO DIGITAL CONVERTER

Since you are using ADC as well as I/O registers (PADATDIR), in case you haven’t done it already, you should be adding the relative header files in the include section of “DSP24_DefaultISR.h”: #include "DSP24_Adc.h" #include "DSP24_Gpio.h"

Although not necessary, it is also advisable to add a declaration for the new ISR in “DSP24_DefaultISR.h”. We now need to add a case branch in the switch statement inside the general interrupt service routine for core interrupt INT1 (see Figure 6-9 for the relative interrupt vectors):

Figure 6-9. Interrupt Vector and Core Level Group for the ADC high priority interrupt9. interrupt void INT1_GISR(void){

New code for INT1 GISR

unsigned int peripheral; asm(" SETC INTM"); peripheral = *PIVR; asm(" CLRC INTM"); switch (peripheral) { case 0x0004: // ADC High Priority interrupt ADCINTHP_ISR(); break; } // reset the core INT1 flag by writing a "1" in bit 1 of IFR *IFR|=1; } 9

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

132

THE ANALOG TO DIGITAL CONVERTER

Save the changes you made. We are almost ready for a test run! 6.2.10 I/O initialization We will be using I/O pin A0 to toggle its state inside the ADC interrupt. We need to configure the pin for general I/O function as an output and assign an initial state (LOW). Open file “DSP24_Sys.c” and replace the code in initIO() function with the following: void initIO(void) { MCRAbits->bit.SCITXD_GPIOA0 = 0;

// Pin A0 is regular I/O

PADATDIRbits->bit.A0DIR = 1;

// Pin A0 is Output

PADATDIRbits->bit.IOPA0 = 0;

// Set A0 to LOW

}

6.2.11 The main() function

GPIOA0 initialization

The new main() function simply calls the system, I/O and ADC initialization and starts an infinite loop, much like in all our previous examples. Create a file named “ADCmain.c” and type-in the following: extern void initSystem(void); extern void initIO(void); extern void initADC_CS(void); void main(void) { initSystem(); initIO(); initADC_CS(); // enable interrupts asm(" CLRC INTM"); // main loop while (1) { } }

Save the changes and add the file to the project. You project view window should like the one shown in Figure 6-10. If all went nicely, you should be able to build, download and run the program.

133

THE ANALOG TO DIGITAL CONVERTER

Figure 6-10. Project browser window for project “ADC-CS”.

6.2.12 Executing the program Let us first verify that the ADC is converting as configured. Download the program and add a breakpoint inside the ADC interrupt as shown in Figure 6-11.

Figure 6-11. Breakpoint inside the ADC Interrupt Service Routine.

Before running, connect pin 45 (3.3 V) in the expansion connector (Figure 6-12) with ADCIN1 pin (or any other channel you wish) in the analog connector (Figure 6-13) on the DSP board. Also, connect the ADCIN2 pin in the analog header with a GND pin in the I/O connector (Figure 6-14). We will be using 3.3V as input voltage for ADCIN1 and GND as input voltage for ADCIN2.

134

THE ANALOG TO DIGITAL CONVERTER

Figure 6-12. Pin 45 (3.3V) on the DSP board expansion connector10.

Figure 6-13. The DSP board ADC connector layout (ADCIN1 and ADCIN2 circled)10.

10

eZdsp™ LF2407A– Technical Reference (Spectrum Digital)

135

THE ANALOG TO DIGITAL CONVERTER

Figure 6-14. GND locations on the DSP board I/O header11.

Be VERY careful with you connections. If you are certain that 3.3V is connected to ADCIN1 and GND to ADCIN2, run your program. If all went well, execution should stop inside the ADC interrupt service routine. Run the program several times in order to make sure that the sequencer actually restarts (recall that we chose continuous run). Now, open a watch window and add array results if not already there. If you expand the contents of the array, they should look like the ones in Figure 6-15.

Figure 6-15. Watch window with the ADC results upon a breakpoint in the ADC ISR.

Notice that result in position 2 is 0, while the result in position 1 is 1018. Given that we are using a 10-bit ADC, in a scale 0-1023, 1018 is very close to the maximum voltage measurement (3.3V). The rest of the results vary from 100-500 since the pins are floating. Generally, if you

11

eZdsp™ LF2407A– Technical Reference (Spectrum Digital)

136

THE ANALOG TO DIGITAL CONVERTER

are getting a result over 1000 for ADCIN1 and a result below 20 for ADCIN2, you can call it a successful test! 6.2. 13 Verifying expected time lapse between consecutive conversion sessions Recall that we have configured the ADC clock to match the CPU clock at 40 Mhz. Also, the acquisition window time prescale was set to 9, giving an acquisition window length of . The overall time required for a single sample should then by calculated as:

Since the cascaded sequencer performs 16 auto-conversions in one session, the overall time for one full session should be:

We may now put our calculations to the test by measuring the time between two consecutive ADC interrupts which should be roughly equal to the time found above (11.6 μs). Recall that the ADC ISR toggles the state of general I/O pin A0; connect your oscilloscope probe to GPIOA0 and remove the breakpoint from the ADC ISR. Before your run the program, comment-out the for-loop that copies the conversion results into the array inside the ADC ISR as shown in Figure 6-16, as it may affect time measurements with the scope. You should now save the file, rebuild and download.

Figure 6-16. Removing the result copying loop from the ADC ISR.

If you downloaded, run the program. Switch on your oscilloscope and try to measure the length of a low or high period on the pin as shown in Figure 6-17.

137

THE ANALOG TO DIGITAL CONVERTER

Figure 6-17. Measurement of time between consecutive ADC interrupts (12.2 μs).

The time measured with the oscilloscope is approximately 12.2 μs, which is close enough to the expected value (11.6 μs), given that some time is spent for the execution of a few statements inside the interrupt service routines (switch branching, I/O pin toggling). For greater acquisition windows, this delay should be rather negligible.

6.3 Using the ADC in Dual Sequencer Mode Although sounds a bit more complicated, in dual sequencer mode you simply program two different sequencers for a number of up to 8 conversions. The two sequencers cannot operate concurrently; however, you may arbitrate their operation by stopping one and starting the other according to your needs. As an example, imagine that you are controlling a robot with differential drive using an analog gyro for rotation feedback and an analog accelerometer for linear motion feedback. You can setup sequencer 1 for a single sample conversion session from the gyro and sequencer 2 for a single sample conversion session from the accelerometer. Whenever the robot is rotating, you may start sequencer 1 (and stop sequencer 2) while upon linear motion you may start sequencer 2 (and stop sequencer 1). It can be inferred from the above, that if you enable the continuous run bit (CONT RUN), sequencers will mutually exclude each other’s operation; therefore, you should configure the ADC for start-stop operation. All in all, think of the two sequencers as two distinct ADC configurations available in your program, one at a time. 138

THE ANALOG TO DIGITAL CONVERTER

6.3.1 A new project for ADC operation in Dual Sequencer Mode We have most of the required “ingredients” to create a project for ADC operation in dual sequencer mode. Create a new project folder named “ADC-DS” and copy the following files from “ADC-CS”: “DSP24_Core.h”, “DSP24_Core.c”, “DSP24_Adc.h”, “DSP24_Adc.c”, “DSP24_Sys.c”, “DSP24_DefaultISR.h”, “DSP24_DefaultISR.c”, “cvectors.asm” and “2407_cmd.cmd”. Create a new project named “ADC-CS” and add the files. Make sure you removed all the references to header files that are not used in the project. Lastly, add library file “rts2xx.lib”. 6.3.2 Configuring the ADC for Dual Sequencer Operation Mode The configuration includes exactly the same steps seen previously in function initADC_CS(). Create a new C file named “ADCFuncDS.c” (or simply copy “ADCFuncCS.c” and rename it) and type the following code for ADC initialization: #include "DSP24_Core.h" #include "DSP24_Adc.h" void initADC_DS(void) { // 1. Configuring ADCTRL1 ADCTRL1bits->bit.STESTENA = 0; ADCTRL1bits->bit.HILO = 0; ADCTRL1bits->bit.BRGENA = 0; ADCTRL1bits->bit.CALENA = 0;

// // // //

ADCTRL1bits->bit.SEQCASC = 0;

// use dual sequencer mode

ADCTRL1bits->bit.INTPRI = 0;

// high priority ADC interrupt

ADCTRL1bits->bit.CONTRUN = 0;

// Start-stop mode

ADCTRL1bits->bit.CPS = 1;

// Conversion ClOCK = CLKOUT/2

ADCTRL1bits->bit.ACQPS = 15; ADCTRL1bits->bit.SOFT = 0; ADCTRL1bits->bit.FREE = 0; ADCTRL1bits->bit.RESET = 0;

self-test mode disabled no effect in normal mode no effect in normal mode calibration mode disabled

Dual Sequencer Mode

Start-Stop mode

This time ADCCLK = CLKOUT/2

// S/H window time = 32*Tclk=64/Clkout // immediate stop on suspend // no action

S/H window is 32*TCLK this time

// 2. Configuring ADCTRL2 ADCTRL2bits->bit.EVBSOCSEQ2 = 0; // EVB cannot start sequencer2 ADCTRL2bits->bit.INTFLAGSEQ2 = 1; // clear SEQ2 interrupt flag ADCTRL2bits->bit.INTENASEQ2 = 1;

// enable SEQ2 interrupt

ADCTRL2bits->bit.SOCSEQ2 = 0; // ADCTRL2bits->bit.RSTSEQ2 = 0; // ADCTRL2bits->bit.EXTSOCSEQ1 = 0; // ADCTRL2bits->bit.EVASOCSEQ1 = 0; //

Enable SEQ2 interrupt

ignored in cascaded mode no action no action EVA cannot trigger SOC on SEQ1

139

THE ANALOG TO DIGITAL CONVERTER ADCTRL2bits->bit.INTFLAGSEQ1 = 1; // clear interrupt SEQ1 flag ADCTRL2bits->bit.INTENASEQ1 = 1; // enable SEQ1 interrupt ADCTRL2bits->bit.SOCSEQ1 = 0; // no action (clear pending SOC triggers) ADCTRL2bits->bit.RSTSEQ1STRTCAL = 0; // no action ADCTRL2bits->bit.EVBSOCSEQ = 0; // EVB does not start cascaded sequencer // 3. MAXimum CONversion number (MAXCONv) *MAXCONV = 0x0044; // 4 conversions for each sequencer // 4. Input Channel Select Sequencing // Sequencer 1 channels CHSELSEQ1bits->bit.CONV00 = 0; CHSELSEQ1bits->bit.CONV01 = 1; CHSELSEQ1bits->bit.CONV02 = 2; CHSELSEQ1bits->bit.CONV03 = 3; // Sequencer 2 channels CHSELSEQ3bits->bit.CONV08 CHSELSEQ3bits->bit.CONV09 CHSELSEQ3bits->bit.CONV10 CHSELSEQ3bits->bit.CONV11

= = = =

Four conversions for EACH sequencer MAXCONV configure

8; 9; 10; 11;

// 6. enable interrupts // enable int1 core interrupt (for ADC high priority interrupt) *IMR |= 0x0001; // 7. Start the first sequencer ADCTRL2bits->bit.SOCSEQ1 = 1; // start sequencer 1 }

The code is very similar to the one in initADC_CS() and you may simply copy it and make the appropriate changes highlighted above. To summarize the differences, the cascaded sequencer bit (CASC SEQ) is set to “0”, continuous run is disabled (CONT RUN = 0), the sequencer 2 interrupt is enabled (INT ENA SEQ2 = 1) and the first three bits in MAXCONV contain the number of conversions for sequencer 1 (4), while bits 3-6 contain the number of conversions for sequencer 2 (4); hence, this time we are only configuring channels in CHSELSEQ1 (the 4 channels for sequencer 1) and CHSELSEQ2 (the 4 channels for sequencer 2). Additionally, just to change settings for demonstration purposes, the ADC clock prescaling is now set for half the period of the CPU clock (CPS = 1) and the acquisition window time is 32TCLK (ACQPS = 15). 6.3.3 The ADC Interrupt Service Routine in Dual Sequencer Mode Since we now have two sequencers, we have to think of a way to arbitrate their operation. A good solution is to start one of the sequencers (i.e., sequencer 1) and then, upon the ADC interrupt, reset it and start the other sequencer. This way, the sequencers will be operating in turns indefinitely. Obviously, this is not a general rule on how to synchronize the operation of the two sequencers, but it is a nice way of demonstrating the principles in 140

THE ANALOG TO DIGITAL CONVERTER

managing interrupts, auto-conversion triggering and sample of results in dual sequencer operation mode. Open file “DSP24_DefaultISR.c” and replace the old code in the ADC ISR with following: interrupt void ADCINTHP_ISR() { unsigned int resultSEQ1[8], resultSEQ2[8], i; // Checking for a Sequencer 1 End of Conversion if (ADCTRL2bits->bit.INTFLAGSEQ1 == 1) { // Sequencer 1 results for (i=0; ibit.INTFLAGSEQ1 = 1; // resetting sequencer 1 to a pre-triggered initial state ADCTRL2bits->bit.RSTSEQ1STRTCAL = 1; // starting Sequencer 2 ADCTRL2bits->bit.SOCSEQ2 = 1; } // Checking for a Sequencer 2 End of Conversion if ( ADCTRL2bits->bit.INTFLAGSEQ2 ==1) { // Sequencer 1 results for (i=8; ibit.INTFLAGSEQ2 = 1; // Resetting Sequencer 2 to a pre-triggered initial state ADCTRL2bits->bit.RSTSEQ2 = 1; // starting sequencer 1 ADCTRL2bits->bit.SOCSEQ1 = 1; } }

The ISR uses two if-statements to acknowledge which of the two sequencers caused the interrupt. Accordingly, it copies the results from the relative result registers, resets the interrupt flag and resets the sequencer (without starting it) by writing a “1” to the corresponding reset bit (RST SEQ1/START CAL for sequencer 1 and RST SEQ2 for sequencer 2); eventually, it starts a conversion on the other sequencer. Notice that results from sequencer 1 are copied from consecutive locations starting at the location of the first result register, whereas results from sequencer 2 are copied from four consecutive locations starting at the location of the 8-th result register. Generally, make sure to read results from locations that actually correspond to the configuration of the sequencer (e.g., if sequencer 1 is configured for 2 conversions, you shouldn’t be reading the third result register), as the DSP “sees” this 141

THE ANALOG TO DIGITAL CONVERTER

as an attempt to read data that has not been prepared for access and it will most likely cause a non-maskable interrupt. 6.3.4 The main() function The main function is practically identical to the one in the previous example. You can either copy file “ADCmain.c” and make the changes shown in rectangles below, or simply create a new “ADCmain.c” file and type the following code: extern void initSystem(void); Dual Sequencer ADC initialization function

extern void initADC_DS(void); void main(void) { initSystem();

Call to Dual Sequencer ADC initialization function

initADC_DS(); // enable interrupts asm(" CLRC INTM"); // main loop while (1) { } }

Notice the absence of initIO() function since we are not using any of the general I/O pins. Remember to remove all references to the “DSP24_Gpio.H” header file (as well as to any header file not used in the project). Your project browser window should look like in Figure 618.

Figure 6-18. Project Browser Window for project “ADC-DS”.

142

THE ANALOG TO DIGITAL CONVERTER

6.3.5 Executing the program It is time to build and download. If your program is already loaded in the DSP’s memory, add two breakpoints inside the ADC ISR as shown in Figure 6-19.

Figure 6-19. Breakpoints inside the ADC ISR.

You should now run the program. If all goes well, execution should stop at the breakpoint inside the first if-clause (Figure 6-20), while the next stop should hit the breakpoint inside the second if-clause (Figure 6-21). Generally, execution should suspend interchangeably at the two breakpoints.

Figure 6-20. Execution suspension at first run (SEQ1 End Of Conversion).

143

THE ANALOG TO DIGITAL CONVERTER

Figure 6-21. Execution suspension after the second run (SEQ2 End Of Conversion).

6.4 Summary/Conclusions The analog to digital converter and the event managers are two of the most important reasons that the C2000™ platform earned their name as DSP controllers. These two peripherals offer a vast variety of options in terms of configuration, combined with great performance. Their design is such, as to accommodate optimized configurations for very specific, state-of-the-art solutions to problems in modern digital signal processing. It is highly recommended that you go through the relevant chapter in the TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals, since there is a great deal of information yet to be acquired on the ADC.

144

THE ANALOG TO DIGITAL CONVERTER

145

THE SERIAL COMMUNICATIONS INTERFACE

7

The Serial Communications Interface

7.1 Overview of the Serial Communications Interface The 2407A implements serial communications with the SCI module. The SCI supports asynchronous serial communications with other peripherals or controllers. It is typically configured for a specific baud rate, number of data bits, 1 or 2 stop bits, even/odd/no parity, and handshaking/no handshaking, using separate transmit and receive buffer registers. Additionally, the SCI offers two multiprocessor communication modes for multiple addressable devices throughout a single transmit line using addressing bytes. Figure 7-1 illustrates the block diagram of the SCI.

Figure 7-1. Block Diagram of the Serial Communications Interface1.

1

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

146

THE SERIAL COMMUNICATIONS INTERFACE

External connections involve the serial communication data transmit pin (SCITXD) and the serial communication data receive pin (SCIRXD). When transmitting, data is originally stored in the transmitter data buffer register (SCITXBUF) and thereafter, forwarded bit-by-bit to the SCITXD line by the transmit shifting register (TXSHF). In a similar way, upon reception, data is shifted from the SCIRXD pin through the receive shifting register (RXSHF) to the receiver data buffer register (SCIRXBUF). The SCI can be configured by three control registers, whereas data reception can be monitored with the use of a status register. Transmit flags (TXRDY-TXEMPTY) are stored in SCI control register 2 (SCICTL2). You may enable/disable transmit and receive interrupts in either high or low priority. Moreover, you may enable/disable a receive error interrupt. The SCI of the 2407A is typically organized as most microcontroller UART modules; if you have already programmed microcontrollers for serial communications, the example that follows should be very familiar. If you are worried about the multiprocessor communication mode, it is practically a different way of “wrapping” your data before transmitting, but the principles in configuration and operation remain the same. Roughly, using the SCI means programming three control registers, setting-up a baud register value, enabling the receiver interrupt and writing/reading data to/from the transmit/receive buffer register.

7.2 A Program to Transmit and Receive Characters with the SCI An example is always the best way to start “digging” deeper into the details of configuring and using a peripheral. Create a new folder named “sci” and new project with same name in the new folder. Copy files “2407_cmd.cmd”, “cvectors.asm”, “DSP24_Gpio.h”, “DSP24_Gpio.c”, “DSP24_Core.h”, “DSP24_Core.c” , “DSP24_DefaultISR.h”, “DSP24_DefaultISR.c” and “DSP24_Sys.c” from your previous project to the new folder. 7.2.1 Creating a new header file for SCI register structures Unfortunately, we must now create all the data structures for the SCI registers in order to use them. You should not be too much frustrated though. Fortunately, the SCI does not involve a large number of registers if you compare it to the event managers. Also, you’ll be delighted to know that all SCI registers are 8-bit long. Create a new file named “DSP24_Sci.h” and type the following: #ifndef DSP24_SCI_H #define DSP24_SCI_H // Communication control register (SCICCR) struct SCICCR_BITS { unsigned int SCICHAR:3; // 0-2 Character length (11 for 8 bits) unsigned int ADDRIDLE_MODE:1;// 3 ADDR/IDLE Mode

147

THE SERIAL COMMUNICATIONS INTERFACE unsigned unsigned unsigned unsigned

int int int int

LOOPBKENA:1; PARITYENA:1; PARITY:1; STOPBITS:1;

// // // //

4 5 6 7

Loop Back enable Parity enable Even-1 or Odd-0 Parity (if enabled) Number of Stop Bits

}; union SCICCR_REG { unsigned int all; struct SCICCR_BITS bit; }; extern volatile union SCICCR_REG *SCICCRbits; // Control register 1 bit definitions(SCICTL1) struct SCITCL1_BITS { unsigned int RXENA:1; // 0 SCI receiver enable unsigned int TXENA:1; // 1 SCI transmitter enable unsigned int SLEEP:1; // 2 SCI sleep unsigned int TXWAKE:1; // 3 Transmitter wakeup method unsigned int rsvd1:1; // 4 reserved unsigned int SWRESET:1; // 5 Software reset unsigned int RXERRINTENA:1; // 6 Receive Error interrupt enable unsigned int rsvd2:1; // 7 Reserved }; union SCICTL1_REG { unsigned int all; struct SCICTL1_BITS bit; }; extern volatile union SCICTL1_REG *SCICTL1bits; // SCI control register 2 (SCICTL2) struct SCICTL2_BITS { unsigned int TXINTENA:1; // 0 Transmit interrupt enable unsigned int RXBKINTENA:1; // 1 Receiver-buffer break interrupt enable unsigned int rsvd:4; // 5:2 reserved unsigned int TXEMPTY:1; // 6 Transmitter empty flag unsigned int TXRDY:1; // 7 Transmitter ready flag }; union SCICTL2_REG { unsigned int all; struct SCICTL2_BITS bit; }; extern volatile union SCICTL2_REG *SCICTL2bits; // Receiver status register (SCIRXST) struct SCIRXST_BITS { unsigned int rsvd:1; // 0 reserved unsigned int RXWAKE:1; // 1 Receiver wakeup detect flag unsigned int PE:1; // 2 Parity error flag unsigned int OE:1; // 3 Overrun error flag unsigned int FE:1; // 4 Framing error flag unsigned int BRKDT:1; // 5 Break-detect flag unsigned int RXRDY:1; // 6 Receiver ready flag unsigned int RXERR:1; // 7 Receiver error flag };

148

THE SERIAL COMMUNICATIONS INTERFACE

union SCIRXST_REG { unsigned int all; struct SCIRXST_BITS bit; }; extern volatile union SCIRXST_REG *SCIRXSTbits; // Priority control register (SCIPRI) struct SCIPRI_BITS { unsigned int rsvd:3; // 0-2 reserved unsigned int FREE:1; // 3 Free emulation suspend mode unsigned int SOFT:1; // 4 Soft emulation suspend mode unsigned int SCIRXPRI:1;// 5 (0-high priority, 1-low priority) unsigned int SCITXPRI:1;// 6 (0-high priority, 1-low priority) unsigned int rsvd1:1; // 6 (0-high priority, 1-low priority) }; union SCIPRI_REG { unsigned int all; struct SCIPRI_BITS bit; }; extern volatile union SCIPRI_REG *SCIPRIbits; extern extern extern extern extern

volatile volatile volatile volatile volatile

unsigned unsigned unsigned unsigned unsigned

int* int* int* int* int*

SCIHBAUD;// SCILBAUD;// SCIRXEMU;// SCIRXBUF;// SCITXBUF;//

SCI SCI SCI SCI SCI

baud-select reg, high byte baud-select reg, low byte emulation data buffer register receiver data buffer register transmit data buffer register

#endif

Save the file. We will soon be going through each of those registers. Now, we need to declare the registers in a .c file. Create a new file named “DSP24_Sci.c” for the declarations and type the following: #include "DSP24_Sci.h" // Communication Control Register volatile union SCICCR_REG *SCICCRbits=(void*)0x7050; // SCI Control Register 1 volatile union SCICTL1_REG *SCICTL1bits=(void*)0x7051; //SCI Control Register 2 volatile union SCICTL2_REG *SCICTL2bits=(void*)0x7054; //SCI Receiver Status Register volatile union SCIRXST_REG *SCIRXSTbits=(void*)0x7055; //SCI Priority Register volatile union SCIPRI_REG *SCIPRIbits=(void*)0x705F; // SCI baud-select register, high byte volatile unsigned int* SCIHBAUD=(void*)0x7052; // SCI baud-select register, low byte volatile unsigned int* SCILBAUD=(void*)0x7053; // SCI emulation data buffer register

149

THE SERIAL COMMUNICATIONS INTERFACE volatile unsigned int* SCIRXEMU=(void*)0x7056; // SCI receiver data buffer register volatile unsigned int* SCIRXBUF=(void*)0x7057; // SCI transmit data buffer register volatile unsigned int* SCITXBUF=(void*)0x7059;

Save the file. This pretty much wraps-up the SCI registers! There’s nothing more to add! It is now time to go through the details of each one of those registers. 7.2.2 The SCI Communication Control Register (SCICCR) The first structure defined in “DSP24_Sci.h” corresponds to the communication control register (SCICCR). The communication control register configures the character format, protocol and communications mode used by the SCI. Figure 7-2 shows a description of the register bits.

Figure 7-2. The SCI Communication Control Register (SCICCR)2. The SCICHAR0-SCICHAR2 bits. Bits 0-2 define the SCI character length (i.e., “0” for 1 bit, “1” for 2 bits, …, “7” for 8 bits). The ADDR/IDLE MODE bit. Bit 3 concerns the multiprocessor mode protocol. A “0” corresponds to idle-line mode protocol, whereas “1” corresponds to address bit protocol. When operating in multiprocessor mode, if the ADDR/IDLE bit is “1”, an address bit is added to the data. The SCI operation remains the same, either in multiprocessor or normal mode. The actual difference between the two modes is the way data are “packed”. In multiprocessor mode, the transmitter should initially transmit a byte (character) representing the address of the receiver. The receiver “wakes up” when it sees its own address in the common transmission line. Adding an address bit to the stream will help distinguishing an address byte from a data byte. On the other hand, when “talking” to only one serial device (which is the case 99% of the times), the ADDR/IDLE bit becomes obsolete. Practically, when using the SCI for normal serial communications, you should be setting this bit to “0”.

2

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

150

THE SERIAL COMMUNICATIONS INTERFACE

The LOOKBACK ENA bit. Bit 4 enables/disables the loopback test mode. If “0”, loopback mode is disabled; if “1”, the transmit and receive signals are connected internally (loopback). The PARITY ENABLE bit. Bit 5 enables the use of parity check bits. If “0”, parity is disabled; if “1”, parity is enabled. The EVEN/ODD PARITY bit. Bit 6 defines the type of parity checksum. A “0” value corresponds to odd parity, whereas a “1” value corresponds to even parity. The STOP BITS bit. Bit 7 defines the number of stop bits. A “0” corresponds to 1 stop bit, whereas “1” corresponds to 2. 7.2.3 The SCI Control Register 1 Bits in SCI control register 2 (SCICTL2) are shown in Figure 7-3.

Figure 7-3. The SCI Control Register 23.

The RXENA bit. Bit 0 enables (“1”)/disables (“0”) the SCI receiver. The TXENA bit. Bit 1 enables (“1”)/ disables (“0”) the SCI transmitter. The SLEEP bit. Bit 2 enables (“0”)/ disables (“0”) the receiver sleep status. Do not worry much about this bit, unless you are using multiprocessor mode. During normal serial communications, keep it to “0”. The TXWAKE bit. Bit 3 defines the transmit feature. Practically, this bit defines an extra transmission action prior to data transmission. The type of action depends on the value of the ADDR/IDLE bit in SCICCR. If ADDR/IDLE is “0” (Idle-Line Protocol), then if you write a “1” to TXWAKE and thereafter place data in the transmit buffer, the transmitter will generate an idle period of 11 data bits (think of it as an “empty” transmission). If the ADDR/IDLE bit is “1”, then if you write a “1” to TXWAKE and place something in the transmit buffer, the 3

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

151

THE SERIAL COMMUNICATIONS INTERFACE

transmitter will set the address bit to “1” for your data (in other words, the data you placed in the buffer should be an address). All in all, keep this feature disabled (TXWAKE = 0) when doing the usual serial “transactions”. The SW RESET bit. Bit 5 is the software reset bit. Writing a “0” to this bit clears most of the flags found in the SCI control register 2 (SCISCTL2) and the receiver status register (SCIRXST), without affecting the configuration of the SCI. Note that the bit holds the flags in reset until a “1” is written to it. You must write a “1” to the SW RESET bit in order to restore flag operation. Table 7-1 shows the flags affected by the SW RESET bit.

Table 7-1. Flags affected by the SW RESET bit4.

The RXERR INT ENA bit. Bit 6 enables the receiver error interrupt. The receiver error interrupt can be triggered by a loss of a stop bit (framing error), a parity error, a break in transmission, or whenever the CPU is trying to read the receiver buffer before the shifting register has completed shifting (see section 7.2.4 about the SCI receiver status register). Keep in mind that, if enabled, the receiver error interrupt is NOT a separate interrupt signal. In fact, it also triggers a receiver interrupt, therefore you will have to add some code to your interrupt service routine to examine whether the interrupt was triggered by an error, or by successful data reception. Of course, if you choose to disable the RXERR interrupt, yet enable the receiver interrupt, interrupt triggering will obviously concern data reception only.

4

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

152

THE SERIAL COMMUNICATIONS INTERFACE

7.2.4 The SCI Control Register 2 The SCI control register 2 (Figure 7-4) contains all SCI interrupt mask bits (receive ready/break detect-RXREADY/BKDETECT, transmit ready-TXREADY) and the transmitter flags (transmitter ready-TXREADY, transmitter empty-TXEMPTY).

Figure 7-4. The SCI Control Register 25.

The TX INT ENA bit. Bit 0 enables the SCI transmit interrupt. When enabled, the TXRDY (ready to transmit) flag triggers the interrupt. The RX/BK INT ENA bit. Bit 1 enables the receive interrupt. If enabled, the interrupt may be triggered either by a data receive event or by a break detect event. Should you choose to enable the interrupt, it is important to read the corresponding status register bit (as shown shortly) to establish which of the two events triggered the interrupt. The TX EMPTY bit. Bit 6 indicates whether the transmitter buffer is empty. It is not related to an interrupt. If the bit is “1”, both transmitter buffer and shift register are empty. The TXRDY bit. Bit 7 indicates whether the transmitter buffer is ready to receive new data for transmission. The TXRDY bit also triggers the transmit interrupt (if enabled). 7.2.5 The SCI Receiver Status Register The receiver, unlike the transmitter, has a flag register of its own (Figure 7-5), called the receiver status register (SCIRXST) including several event flags, some of which are interrupt flags as well.

Figure 7-5. The SCI Receiver Status Register (SCIRXST)5.

5

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

153

THE SERIAL COMMUNICATIONS INTERFACE

The RXWAKE bit. Bit 1 is the receiver wakeup detect flag, indicating the presence of an address bit in the data when operating in multiprocessor mode with an address bit; in any other case, this bit reflects idle data line detection. Although not very useful when performing regular serial communications, this flag is important in multiprocessor mode, since it provides information about the type of the received packets (address or regular data). The PE bit. Bit 2 is the parity error flag. It has a value of “1” if a parity error was detected in the stream. If parity is disabled, it is always “0”. The OE bit. Bit 3 is the overrun error flag. An overrun error occurs when the shifting register shifts data into the buffer register before the previous character was fully read by the CPU. The FE bit. Bit 4 is the framing error flag. A framing error occurs when a stop bit has not been received as expected. The BRKDT bit. Bit 5 is the break detect flag. A break detect occurs when the receiver has been low for at least ten bits, following a missing stop bit (framing error). The RXRDY bit. Bit 6 is the receiver ready flag. This flag indicates that the receiver buffer register contains data ready to be read. It also triggers the receive interrupt (if enabled). The RX ERROR bit. Bit 7 is the receiver error flag. This flag becomes “1” whenever an overrun error (OE flag), framing error (FE flag), parity error (PE) or break detect (BRKDT) has occurred. It also triggers the receiver error interrupt (if enabled). 7.2.6 The SCI Priority Register The SCI priority register (SCIPRI) defines the priority of the SCI interrupts (high/low) and the behavior of the peripheral upon execution suspension (i.e., breakpoints).

Figure 7-6. The SCI Priority Register6.

6

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

154

THE SERIAL COMMUNICATIONS INTERFACE

The SCI FREE-SOFT bits. Bits 3 and 4, much like in most peripherals already examined in the text, define what happens when an emulation suspension occurs. If both FREE and SOFT are “0”, the SCI will stop immediately upon emulation suspension; if SOFT is “1” and FREE is “0”, the SCI will complete any pending transmissions/receptions and stop upon emulation suspension; if the FREE bit is “1”, then the SCI will continue operation regardless of any suspension. The SCI RX PRIORITY bit. Bit 5 configures the priority of the receiver interrupt. If “0”, the receiver interrupt is configured for high priority requests, whereas if “1”, the receiver interrupt is configured for low priority requests. Be cautious with what kind of priority you configure the receive interrupt when you write your interrupt service routine! If the priority is low, the receiver interrupt corresponds to core interrupt 5 (INT5), whereas if high, it corresponds to core interrupt 1 (INT1). The SCI TX Priority bit. Bit 6 defines the priority of the transmitter interrupt. Priority can either be high (“0”) or low (“1”). Much like the case of the receiver interrupt priority, If the transmitter interrupt is set to low priority mode, it then corresponds to a core interrupt 5 (INT5), whereas if high, it corresponds to a core interrupt 1 (INT1). 7.2.7 Baud-Select Registers (SCIHBAUD – SCILBAUD) The baud registers configure the baud rate of your serial communications. As shown in Table 7-2, the baud rate is defined with a 16-bit word. Since all SCI registers are 8-bit long, we have two 8-bit baud registers (Figure 7-7), therefore the low byte should be stored in the SCI low byte BAUD register (SCILBAUD), whereas the high byte should be stored in the SCI high byte BAUD register (SCIHBAUD).

Table 7-2. 16-bit Baud Register values for common bit rates (CPU clock at 40 MHz)7.

7

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

155

THE SERIAL COMMUNICATIONS INTERFACE

Assuming that the desired baud rate is BaudRate and the CPU clock frequency CLKOUT, then the value of the baud register (high and low byte) BRR, can be calculated using the following:

For example, a desired baud rate at 19200 with a DSP internal clock frequency 40 MHz (x4 PLL prescaling) will give a baud register value,

If you take a look at Table 7-2, the suggested value of the baud register for a 19231 baud value is 259, fairly close to what we found using the above equation. In most cases, the 2407A is configured for internal CPU clock at about 40 MHz, therefore Table 7-2 will directly give you the baud register values for common bit rates. However, there may be cases in which the CPU internal clock will be configured at different frequencies, thereby constituting the table inaccurate. Either way, you should always use the formula to make sure you have the correct values for the baud register.

Figure 7-7. The SCI Low byte Baud Register and High byte Baud Register8.

Keep in mind that you cannot access the baud registers as one 16-bit word. Each register occupies an entire data memory word (addresses 0x7052 and 0x7053), however, only the lower 8 bits of the memory word are taken into consideration. Hence, to configure the baud registers, you should store the lower byte of the BRR value in SCILBAUD and the higher 8

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

156

THE SERIAL COMMUNICATIONS INTERFACE

byte of the BRR value in the SCIHBAUD (e.g., for a baud rate of 9600, SCILBAUD = 0x08 and SCIHBAUD = 0x02). 7.2.8 Receiver Buffer Register and Transmitter Buffer Register To transmit data, simply place a byte in the transmitter buffer register (SCITXBUF). Before placing your data, make sure that you have checked the TXRDY (transmitter ready) flag in SCI control register 2. Following transmission, the TXRDY flag is automatically cleared and therefore, you should not clear it manually. In quite the same way, whenever data has been received and the shifting register has finished shifting into the buffer, the RXRDY (receiver ready) flag in the SCI receiver status register (SCIRXST) is raised and you may read the data from the receiver buffer register (SCIRXBUF). Keep in mind that you MUST read the contents of the buffer register, not only to retrieve the data, but also to reset the RXRDY flag. Unlike other peripherals we have encountered so far, you should NOT clear the flags that caused an interrupt whether it is a transmitter or a receiver one. In order to track the data of the receiver buffer register on a watch window, you may use the emulation receiver buffer register (SCIRXEMU). SCIRXEMU is a virtual register sharing the same content with the receiver buffer register; however, read actions from this register do not clear the RXRDY (receiver ready) flag. The SCIRXEMU is very useful for debugging purposes, since you may read the contents of the receiver buffer register without clearing the RXRDY flag. 7.2.9 The main() function of project “sci” We are now ready to get on with our project. We will be setting up the SCI for simple serial communications at 19200 baud, No Parity, 1 Stop Bit with the receiver interrupt enabled. Create a file named “scimain.c” and type the following: extern extern extern extern

void void void void

initSystem(void); initIO(void); initSCI(void); sendChar(char c);

void main(void) { int i; char ch; initSystem(); initIO(); initSCI(); // enable global interrupts asm(" CLRC INTM");

157

THE SERIAL COMMUNICATIONS INTERFACE // main loop while(1) { for (i=0; ibit.ADDRIDLE_MODE = 0; // Idle mode (no wake bit in frame) SCICCRbits->bit.LOOPBKENA = 0; // Loop back disabled SCICCRbits->bit.PARITYENA = 0; // Parity Disabled SCICCRbits->bit.PARITY = 0; // No effect (0 is Odd) SCICCRbits->bit.STOPBITS = 0; // 1 Stop bit // Configuring Baud rate to 19200 -> BRR = 0x0103 *SCILBAUD = 0x03; *SCIHBAUD = 0x01; // Configuring SCI control register 1 SCICTL1bits->bit.RXENA = 1; // enable receive SCICTL1bits->bit.TXENA = 1; // enable transmit SCICTL1bits->bit.SLEEP = 0; // Sleep mode disabled SCICTL1bits->bit.TXWAKE = 0; // Transmit feature not selected SCICTL1bits->bit.RXERRINTENA = 1; // enable error interrupt // Configuring SCI Control Register 2 SCICTL2bits->bit.TXINTENA = 0; // disable transmit interrupt SCICTL2bits->bit.RXBKINTENA = 1; // Enable receive interrupt // Configuring interrupt priority SCIPRIbits->bit.SCIRXPRI = 1; //Low receiver interrupt priority SCIPRIbits->bit.SCITXPRI = 1; //Low transmitter interrupt priority

158

THE SERIAL COMMUNICATIONS INTERFACE SCIPRIbits->bit.FREE =0; SCIPRIbits->bit.SOFT = 0; // immediate stop on suspend // Reseting registers (configuration not affected) SCICTL1bits->bit.SWRESET = 0; // reset SCICTL1bits->bit.SWRESET = 1; // release from reset // Unmasking core interrupt 5 *IMR |= 0x10; } void sendChar(char c) { while (SCICTL2bits->bit.TXRDY!=1); // wait for transmit ready *SCITXBUF = (int)c; }

The communication control register (SCICCR) is configured first. Field SCICHAR receives a value of 7 (“111”) corresponding to 8-bit long data characters. The ADDRESS/IDLE bit is set to “0”, since we are not going to be using multiprocessor mode. The LOOPBK, PARITYENA and PARITY bits are set to “0” for no loopback operation (meaning no internal shorting of SCIRX and SCITX pins), no parity check; although we select odd parity, the setting has no effect since parity was disabled earlier in the configuration. Finally, a value of “0” in STOPBITS corresponds to a setting of 1 stop bit. We are going to be using a baud rate of 19200. If you look at Table 7-2 (or consider our example using the formula to calculate the baud register value for a 19200 baud rate), this rate corresponds to a hexadecimal value of 0x0103 for the baud register. Recall that we cannot assign this number directly to both low and high baud registers; therefore, we perform separate assignments. Hence, *SCILBAUD = 0x03;

is the low byte assignment for the SCI low byte BAUD register and, *SCIHBAUD = 0x01;

is the high byte assignment for the SCI high byte BAUD register. The SCI control register 1 (SCICTL1) enables transmitter and receiver operation (TXENA=1, RXENA=1) and disables sleep mode (not using multiprocessor communications mode). Also, TXWAKE transmit feature is disabled (not using multiprocessor mode). Finally, we assign a value of “1” to RXERRINTENA to enable the receiver error interrupt (if an error occurs, the receiver interrupt, RXINT, will be triggered). 159

THE SERIAL COMMUNICATIONS INTERFACE

In SCI control register 2 (SCICTL2), we disable the transmitter interrupt (TXINTENA=0) and enable the receiver/break detect interrupt (RXBKINTENA=1). The last register to configure in initSci() is the priority register (SCIPRI). Notice the value assignments for fields SCIRXPRI and SCITXPRI: SCIPRIbits->bit.SCIRXPRI = 1; //Low receiver interrupt priority SCIPRIbits->bit.SCITXPRI = 1; //Low transmitter interrupt priority

Both interrupt priority bits are set to “1”, corresponding to low priority. This means that SCI interrupts in this example will correspond to core interrupt INT5 and therefore, we should implement branching code for INT5 general interrupt service routine. If you choose high priority for either one of those interrupts, then you will have to implement branching code inside the INT1 general interrupt service routine. Tables 7-3 and 7-4 list the core level interrupts and peripheral interrupt vectors for low priority and high priority SCI interrupts. Finally, both FREE and SOFT bits are set to “0”, corresponding to immediate stop upon emulation suspend.

Table 7-3. Interrupt Vector and Core level group for RX/TX interrupts in low priority mode 9.

Now notice the two value assignments for bit SW RESET in SCICTL1 just before we unmask core Interrupt 5: SCICTL1bits->bit.SWRESET = 0; // reset SCICTL1bits->bit.SWRESET = 1; // release from reset

Recall that the SWRESET bit clears the receiver and transmitter flags, if set to “0”. Following a reset by SWRESET, the flags stay in reset until they are released. In order to release the flags from their reset state, we must write a “1” again to the SWRESET bit. The SWRESET is a 9

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

160

THE SERIAL COMMUNICATIONS INTERFACE

convenient way of re-initializing the SCI flags, but you should always remember to release the flags from reset by writing a “1” to this bit. The last remaining task is to enable the appropriate core interrupt by unmasking the corresponding bit in the interrupt mask register (IMR). Since we configured the SCI priority register for low priority interrupts, we should unmask core interrupt 5 (bit 4 in IMR): *IMR |= 0x10;

Table 7-4. Interrupt Vector and Core level group for RX/TX interrupts in high priority mode 10.

Let us now briefly go through function sendChar(). This function simply places a character in the transmit buffer register (SCITXBUF). while (SCICTL2bits->bit.TXRDY!=1); // wait for transmit ready *SCITXBUF = (int)c;

The while loop “holds back” the assignment statement until the TXRDY (ready to transmit) flag is set (“1”). Usually, if you choose not to wait and just place your data in the transmit buffer you may get away with it. However, when transmitting large quantities of information, there is a chance that data may be placed into the buffer before the transmitter is ready, thus causing inexplicable errors in transmission. Always use the while loop to ensure your transmitter is ready before you place data in the buffer. Alternatively, you may enable the transmitter interrupt and place the data assignment command inside the corresponding ISR, but this makes the approach more complicated than you may want it to be.

10

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

161

THE SERIAL COMMUNICATIONS INTERFACE

7.2.11 I/O Configuration Like in most cases, the serial receive (SCIRX) and serial transmit (SCITX) pins are multiplexed with general I/O pins. In fact, the SCITX pin is multiplexed with port A pin 0 (IOPA0) and the SCIRX pin is multiplexed with port A pin 1 (IOPA1). Therefore, we need to configure them to their primary function. Open file “DSP24_Sys.c”, delete function initIO() and replace it with the following: void initIO(void) { // setting up SCIRXD and SCITXD for serial comms MCRAbits->bit.SCIRXD_GPIOA1 = 1; MCRAbits->bit.SCITXD_GPIOA0 = 1; }

Save the changes. The only thing left is to write code for the interrupts in our program. 7.2.12 Interrupt Service Routines Open file “DSP24_DefaultISR.c”. First delete or comment-out all references to peripherals not used in this application (i.e., event managers, ADC) and remove the relative include statements from “DSP24_DefaultISR.h” by replacing them with an include directive for file “DSP24_Sci.h”: #include "DSP24_Sci.h"

Also, add the declaration for the SCI low priority receiver and transmitter interrupt service routines, RXINTLP_ISR(), TXINTLP_ISR(). interrupt void RXINTLP_ISR(void); interrupt void TXINTLP_ISR(void);

Now, open file “DSP24_DefaultISR.c” and go to the definition of function INT5_GISR() (recall we are using low priority interrupts) and add the following code: interrupt void INT5_GISR(void) { unsigned int peripheral; asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM"); switch (peripheral) { case 0x0006: // SCI Receive RXINTLP_ISR(); break; case 0x0007: // SCI Transmit TXINTLP_ISR(); break;

162

THE SERIAL COMMUNICATIONS INTERFACE default: break; } // reset INT5 flag by writing a "1" in bit 4 of IFR *IFR|=0x10; }

The switch statement branches to either RXINTLP_ISR() or TXINTLP_ISR() depending on the value retrieved from the peripheral interrupt vector register (PIVR). We now need to define these functions. Allocate some space below the capture interrupt service routines and type the following: interrupt void RXINTLP_ISR(void) { char c; int errstatus; errstatus = SCIRXSTbits->bit.RXERR; if (errstatus==1) { // error handling asm(" NOP"); } else { c = *SCIRXBUF; asm(" NOP"); } } interrupt void TXINTLP_ISR(void) { }

Since we disabled the transmit interrupt, we will not be adding code to the corresponding routine. On the other hand, the receive interrupt service routine (RXINTLP_ISR) should be able to distinguish an error interrupt from a buffer ready interrupt and act accordingly. The function stores the value of the RXERR bit in variable errstatus. If errstatus becomes “1”, an error occurred and the code branches to the clause marked with “error handling” comments, otherwise the buffer register value is read and stored in variable c. By now you should have realized that the code inside the general interrupt service routines (INTx_ISR(), x=1, 2, 3, 4, 5, 6) doesn’t change; it is simply a long switch statement that branches to the specific ISRs according to the content of PIVR. You should keep a separate copy of “DSP24_DefaultISR.c” as a template, containing all specific interrupt service routines without code (just opening and closing brackets “{“, “}”), as well as all general interrupt service routines with their code fully implemented (switch statements with branches to all possible SISRs within the same interrupt level). Try to complete the switch statement branches in every GISR by including all peripheral interrupt vectors in each core level interrupt using Table 2-2 in Chapter 2 of the TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals. While looking at this table, you will realize that there are many peripheral interrupts that can be configured for low or high priority requests. For these cases, you could either create separate SISRs for low and high priority (e.g., RXINTLP_ISR for low priority and 163

THE SERIAL COMMUNICATIONS INTERFACE

RXINTHP_ISR for a high priority receive ISR), or make unique ISRs to which the switch statements jump in both high and low priority. 7.2.13 Program Execution Add all files to your project and save it. Make sure you included files “2407_cmd.cmd”, “cvectors.asm”, “DSP24_Sci.h”, “DSP_24Sci.c”, “scimain.c”, “DS24_Core.h”, “DSP24_Core.c”, “DSP24_Sys.c”, “DSP24_Uart.c”, “DSP24_Gpio.h”, “DSP24_Gpio.c” and library “rts2xx.lib”. Your project browser window should look like the one shown in Figure 7-8.

Figure 7-8.Project browser window for project “sci”.

Do a rebuild all and hopefully, you will get a successful compilation message. Otherwise, check your code for mistakes and try again. If the program compiled successfully, connect to the DSP and download it. Following download, do a Reset CPU. Before you run the program, place two breakpoints inside the receiver interrupt service routine as shown in Figure 7-9.

164

THE SERIAL COMMUNICATIONS INTERFACE

Figure 7-9. Breakpoints inside the RXINT low priority ISR.

Obviously, the program will “hit” the first breakpoint only if an error (RXERR flag) triggers the interrupt; in case of a successful reception, execution will be suspending inside the else clause. A nice way of testing your serial communications would be to connect the DSP to your PC, open up a terminal at 19200 baud and type characters to the DSP. Unfortunately, we cannot do this without a buffering circuit; the serial port of the PC uses high voltage signals ranging from -8V to +7V, whereas the DSP uses 0V to 3.3V signals on the SCITX and SCIRX pins. Obviously if you connect them without a buffer circuit, the PC will irreversibly damage the DSP. So, how do we test our program? An effective solution would be to connect the transmit pin (SCITX) to the receive pin (SCIRX) and hopefully, we will be able to see characters ‘A’, ‘B’, …, ‘L’ (recall that in the main loop, 10 consecutive characters , starting from ‘A’, are being repeatedly transmitted) in the receiver buffer upon interrupt. Figure 7-10 shows the physical locations of SCITX and SCIRX pins on the I/O header.

Figure 7-10. Locations of SCI Receive (SCIRX) and SCI Transmit (SCITX) on the DSP board11.

11

eZdsp™ LF2407A– Technical Reference (Spectrum Digital)

165

THE SERIAL COMMUNICATIONS INTERFACE

Carefully short pins 3 and 4 on the I/O header (Figure 7-10) and run the program. If all goes on as planned, execution should suspend stop the RXINTLP_ISR() function as shown in Figure 7-11. Press F5 (run) several times to make sure the program stops inside the SSIR.

Figure 7-11. Execution suspension inside RXINTLP_ISR().

If you place the mouse pointer over variable c, the IDE should display a yellow popup message (Figure 7-11) showing the content of variable c. Press F5 a few times more and then disconnect the pins. Normally, the program should continue execution without suspending stopping at the breakpoints.

6.3 Summary The serial communications interface is very important in embedded programming. Most microcontroller programmers use it to “talk” to their program, in order to either send commands/parameters, or to perform debugging. The DSP does not come with voltage level shifted buffers and therefore, you cannot directly connect it to the serial port of the PC. However, you can easily build a buffer circuit to accommodate connections with a commercially available RS-232 buffer chip. Alternatively, you may connect the DSP to another controller with Rx , TX lines operating at TTL/CMOS voltage levels. The 2407A offers multiprocessor operation mode using a single line to transmit to multiple recipients. Although not many devices out there support this kind of serial communications, it is recommended to “emulate” such a recipient with your program in order to test this mode. It should be a good exercise to improve your acquaintance with the SCI flags.

166

THE SERIAL COMMUNICATIONS INTERFACE

167

THE SERIAL PERIPHERAL INTERFACE

8

The Serial Peripheral Interface

8.1 Overview of the Serial Peripheral Interface The SPI is yet another serial interface on the LF2407A providing synchronous communications as opposed to the asynchronous operation of the SCI. Communications with the SPI require some sort of arbitration between communicating parties, a role which either device can play. Specifically, the device that synchronizes the operation is called the master and it provides a clocking signal synchronizing data transmission; consequently, the other device is called slave.

Figure 8-1. Serial Peripheral Interface block diagram1.

1

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

168

THE SERIAL PERIPHERAL INTERFACE

Other than that, the SPI uses two pins, SPISIMO (SPI Slave In-Master Out) and SPISOMI (SPI Slave Out-Master In) for serial data transmission and reception according to configuration. In practice, many off-the-shelf commercial SPI devices (such as analog to digital converters, LCDs, etc.) do not transmit any data; therefore, in many cases, only one line (either SPISIMO or SPISOMI) is used. Regardless of whether one or both SPI lines are used, master and slave always transmit simultaneously, which explains the fact that there are no separate interrupts for transmission and reception. The operation of data exchange is very much similar to the one in SCI, as there are two input and output buffer registers (SPIRXBUF, SPITXBUF) and two corresponding 4-level deep FIFO stacks out of which, data is eventually shifted in/out the SPISIMO/SPISOMI lines. Figure 8-1 shows the block diagram of the peripheral. To resume the principle of operation of the SPI, assume that the DSP is the master in a connection with another device. In that case, the DSP uses its SPICLK line to initiate transmission at will, while the slave transmits data simultaneously. In other words, the master initiates transmission both ways, using his clock signal. In particular, upon one clock edge, data is shifted into the line, while on the next edge, the shift register “holds back” and the data remains latched. The process repeats until the entire character has been transmitted from both sides. There are certain variations of this clocking scheme, but the principle remains the same. An SPI device, except for the serial in/out line would typically have an active-low strobe line used to enable it by the master; the strobe should be driven low prior to transmission by the master (namely, the pin) and high, immediately following conclusion of the transmission. In short, the master sets the line to “low”, in order to initiate transmission; the line is kept low until the entire character has been transmitted.

8.2 A Program to Transmit characters with the SPI using the DSP as Master It’s about time to move on to an example. The overall functionality is very similar to the one employed in the SCI example: We need to initialize the SPI at a certain bit rate, configure it as a master and enable the interrupt. Create a new folder and project under name, “SPI” and copy the following files: “DSP24_Core.h”,”DSP24_Core.c”, “DSP24_Gpio.h”, “DSP24_Gpio.c”, “DSP24_DeafultISR.h”, “DSP24_DefaultISR.c”, “DSP24_Sys.c”, “cvectors.asm” and “2407_cmd.cmd”. Add these files to your new project and, of course, don’t forget to add the “rts2xx.lib” file. 8.2.1 Creating a new header file for the SPI registers The SPI is not a very extensive peripheral in terms of the number of registers. All registers are 8-bit long; therefore, this is a good chance to complete yet another peripheral 169

THE SERIAL PERIPHERAL INTERFACE

header file to the full extent. Create a new file named “DSP24_Spi.h”, and type-in the following code: #ifndef DSP24_SPI_H #define DSP24_SPI_H //SPI Configuration Control Register (SPICCR) struct SPICCR_BITS { unsigned int CHARLEN:4; // 0-3 Containing Character length in bits unsigned int rsvd:2; // 4-5 reserved unsigned int CLKPOL:1; // 6 clock polarity unsigned int SPIRST:1; // 7 Reset }; union SPICCR_REG { unsigned int all; struct SPICCR_BITS bit; }; extern volatile union SPICCR_REG *SPICCRbits; // SPI Operation Control Register (SPICTL) struct SPICTL_BITS { unsigned int INTENA:1; // 0 Enables Transmit/Receive interrupt unsigned int TALK:1; // 1 Transmission enable. unsigned int MST_SLV:1; // 2 if 0 SPI is slave, 1 for master unsigned int CLKPHSEL:1; // 3 0 - normal clocking,1 - 1.5 cycle delay unsigned int OVRNINTENA:1; // 4 Overrun Int enable unsigned int rsvd:3; // 5-7 reserved }; union SPICTL_REG { unsigned int all; struct SPICTL_BITS bit; }; extern volatile union SPICTL_REG *SPICTLbits; // SPI Status Register (SPISTS) struct SPISTS_BITS { unsigned int rsvd:5; // 0-4 reserved unsigned int TxBUFFULL:1; // 5 Transmit buffer full flag unsigned int SPIINT:1; //6 SPI INT flag unsigned int RxOVRN:1; // 7 Receive ioverrun flag }; union SPISTS_REG { unsigned int all; struct SPISTS_BITS bit; }; extern volatile union SPISTS_REG *SPISTSbits; // SPI Baud Register (SPIBRR) struct SPIBRR_BITS { unsigned int BITRATE:7; // 0-6 SPI bit rate unsigned int rsvd:1; // 7 reserved }; union SPIBRR_REG { unsigned int all;

170

THE SERIAL PERIPHERAL INTERFACE struct SPIBRR_BITS bit; }; extern volatile union SPIBRR_REG *SPIBRRbits; // SPI Baud register as a word extern volatile unsigned int *SPIBRR; // SPI Priority Control Register (SPIPRI) struct SPIPRI_BITS { unsigned int rsvd1:4; //0-3 reserved unsigned int SPIFREE:1; // 4 SPI suspension free bit unsigned int SPISOFT:1; // 5 SPI suspension soft bit unsigned int SPIPRI:1; // 6 SPI interrupt priority (0 high, 1 low) unsigned int rsvd2:1; // 7 reserved }; union SPIPRI_REG { unsigned int all; struct SPIPRI_BITS bit; }; extern volatile union SPIPRI_REG *SPIPRIbits; // SPI Transmit and Receive Buffer Registers (SPITXBUF and SPIRXBUF) extern volatile unsigned int* SPITXBUF; extern volatile unsigned int* SPIRXBUF; #endif

// end definitions

Save the file. As you should be accustomed by now, the next step is to create the corresponding C file “DSP24_Spi.c” containing the declarations of each one of the register structures defined in the header file. Create “DSP24_Spi.c” and add the following: #include "DSP24_Spi.h" volatile union SPICCR_REG *SPICCRbits = (void*)0x7040; volatile union SPICTL_REG *SPICTLbits = (void*)0x7041; volatile union SPISTS_REG *SPISTSbits = (void*)0x7042; volatile union SPIBRR_REG *SPIBRRbits = (void*)0x7044; volatile union SPIPRI_REG *SPIPRIbits = (void*)0x704F; // Non union-type declarations volatile unsigned int *SPIBRR = (void*)0x7044; volatile unsigned int *SPITXBUF = (void*)0x7048; volatile unsigned int *SPIRXBUF = (void*)0x7047;

Save the file and add it to the project. If you scan for dependencies, “DSP24_Spi.h” should appear under the include branch on your project browser window. Let us now examine a little bit more closer each one of the registers defined and declared in the files. 171

THE SERIAL PERIPHERAL INTERFACE

8.2.2 The SPI Configuration Control Register (SPICCR) The SPI configuration control register configures the operation of the SPI in terms of clock polarity-phase and character length. It also includes a reset bit that clears flags and enables/disables transmission.

Figure 8-2. The SPI Configuration Control Register2.

The SPICHAR bits. Bits 0-3 in SPICCR configure the length of a character. Valid lengths range from 1 - 16 and they correspond to values 0 – 15. One of the most common settings is a value of 7 (8 bit characters); however, it is entirely up to the type of SPI device interfaced to the DSP. The CLOCK POLARITY bit. Bit 6 configures how the polarity of the clocking signal (SPICLK) will affect the data transmission. Specifically, if “0”, data is transmitted on a rising edge and held with a “falling edge”. A value of “1” causes the opposite: output is transmitted upon a falling edge and latched with a rising edge (with input transmission). This is the typical scheme of things if the CLOCK PHASE bit in the operation control register is set to “0”. If the CLOCK PHASE bit is set to “1”, the first data transmission precedes the first rising edge if polarity is “0” or the first falling edge if polarity is “1”, by 1.5 clock cycles and thereafter, it occurs at a falling edge (if polarity is set to “0”) or a rising edge (if the polarity is set to “1”). It is a bit confusing, but in practice most setups will involve both CLOCK POLARITY and PHASE set to “0”; therefore, in this case, things will be clear: Output transmission upon rising edge (and latch with a falling edge) and input transmission upon falling edge (and latch with a rising edge). The examples in Figures 8-4, 8-5, 8-6 and 8-7 should give the general idea about how the various configurations work. The SPI SW RESET bit. Bit 7 is used to clear the flags (RECEIVER OVERRUN, SPI INTERRUPT and TX BUFFULL). It has no effect on the configuration of the SPI. If set to “0”, flags are cleared, but also the SPICLK line goes low if the DSP is operating as a master; if SWRESET is

2

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

172

THE SERIAL PERIPHERAL INTERFACE

“1”, SPI is ready to receive or transmit. Use this bit to clear the flags during configuration, but you should remember to write a “1” shortly afterwards. 8.2.3 The SPI Operation Control Register (SPICTL) The SPI operation control register contains additional operation configuration bits, enables SPI interrupts and enables/disables (high impedance/low impedance) the transmission line.

Figure 8-3. The SPI Control Register (SPICTL)3.

The SPI INT ENA bit. Bit 0 in SPICTL enables the SPI interrupt. If “0”, SPI interrupt is disabled; if “1”, SPI interrupt is enabled. The TALK bit. Bit 1 in SCICTL enables/disables transmission in either the output line (if operating as master) or in the input line (if operating as slave). A “0” disables transmission and a “1” enables it. Practically, this bit is used in cases involving more than two SPI devices in the network and therefore the transmission lines will have to be set to high impedance if the chipselect line ( ) will be high (device not selected). The MASTER/SLAVE bit. Bit 2 configures the network mode control. If “0”, device is operating as a slave; if “1”, device is operating as a master. The CLOCK PHASE bit. Bit 3 in SPICTL, in conjunction with the CLOCK POLARITY bit in SPICCR, configures the way that data shifting in and out is synchronized with the SPICLK signal. To visualize this with an example, assume that the 4-bit binary number 1010 is transmitted from the master (DSP) to the slave (an SPI device) and the slave transmits 1101 back. Figures 8-4, 8-5, 8-6 and 8-7 show the transition sequence for a CLOCK POLARITY-CLOCK PHASE configuration of “00”, “01”, “10”, and “11”. A more general description of the various signal options is illustrated in Figure 8-8. To summarize, if the phase bit is “0”, the timing of data output and latching is solely based on the clock; however, if the phase bit is “1”, the first data output is based on the time that the master pulls the line to LOW, while the 3

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

173

THE SERIAL PERIPHERAL INTERFACE

following data outputs and latches are based on the clock. It should be noted however, that the line is always pulled low upon the first data output, regardless of the phase setting. The OVERRUN INT ENA bit. Bit 4 enables the overrun interrupt upon a RECEIVER OVERRUN flag. The overrun flag is raised if the content of the receive buffer register has been overwritten before the previous content has been entirely read. The flag corresponds to the same interrupt vector with SPI INT and therefore triggers an SPI interrupt.

Figure 8-4. Master transmission and reception with 0-clock polarity and 0-clock phase.

Figure 8-5. Master transmission and reception with 1-clock polarity and 0-clock phase.

174

THE SERIAL PERIPHERAL INTERFACE

Figure 8-6. Master transmission and reception with 0-clock polarity and 1-clock phase.

Figure 8-7. Master transmission and reception with 1-clock polarity and 1-clock phase.

175

THE SERIAL PERIPHERAL INTERFACE

Figure 8-8. SPI data input/output synchronization for the various phase and polarity values 4.

8.2.4 The SPI Status Register (SPISTS) The SPI status register contains all flags related to the operation of the SPI.

Figure 8-9. The SPI Status Register (SPISTS)4.

The TX BUF FULL FLAG bit. Bit 5 is a flag indicating that a character has been written to the transmit buffer (SPITXBUF) and is pending transmission. The flag is automatically cleared following transmission of the character. You should use this flag to delay a write-action to the buffer until it is cleared.

4

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

176

THE SERIAL PERIPHERAL INTERFACE

The SPI INT FLAG bit. Bit 6 is a flag that indicates the completion of a transmission or reception. Obviously, if the SPI interrupt is enabled, the flag will trigger the interrupt. Unlike other peripherals, you should NOT try to manually clear the flag. Instead, you should either read the buffer (SPIRXBUF) or use the SPI SW RESET bit. The RECEIVER OVERRUN bit. Bit 7 is a flag indicating that data in SPIRXBUF was overwritten before its content could be read. If the RECEIVER OVERRUN interrupt is enabled, the flag will cause an SPI interrupt. 8.2.5 The SPI Baud Rate Register (SPIBRR) The SPI baud register defines the baud rate of the SPI. Although the register is 8-bit long, only the first seven bits (values from 0 to 127) are used to configure the baud rate. The formula that relates the baud rate with the value of SPIBRR is:

where CLKOUT is the frequency of the CPU clock (i.e., 40 MHz). Apparently, if the CPU clock runs at 40 MHz, the minimum baud rate for the SPI is 312,500 (40,000,000/128) corresponding to a value of 127 in the baud register; the maximum baud rate should be 10,000,000 (40,000,000/4) for any value between 0 and 3 in SPIBRR. 8.2.6 The SPICLK signal in terms of the CLKOUT signal and the value in SPIBRR If the value in SPIBRR is less or equal to 3, the SPICLK period is equally divided into two CLKOUT periods for a LOW pulse and another two CLKOUT periods for a HIGH pulse as shown in Figure 8-10. In quite the same way, if the value of BRR is greater than 3 and SPIBRR+1 is an even number, the SPICLK LOW and HIGH pulses are equal to BRR+1 periods of the CPU clock signal (Figure 8-11). However, if SPIBRR+1 is an odd number, then the length of the low and high pulse of the SPICLK signal is given by:

177

THE SERIAL PERIPHERAL INTERFACE

The above mean that SPICLK can be asymmetrical and the length of the low and high pulses of the signal depends on the polarity. Figures 8-12 and 8-13 shows the correlation between the CLKOUT signal and the SPICLK signal for a BRR value of 4 and CLOCK POLARITY bit equal to 0 and 1 respectively.

Figure 8-10. The SPICLK and CLKOUT signals for BRR less or equal to 2.

Figure 8-11. The SPICLK and CLKOUT signals for BRR = 4 and clock polarity 0.

Figure 8-12. The SPICLK and CLKOUT signals for BRR = 4 and Clock Polarity 1.

178

THE SERIAL PERIPHERAL INTERFACE

8.2.7 The SPI Priority Register (SPIPRI) The SPI priority register (SPIPRI) configures the SPI interrupt priority level and the operation of the peripheral upon emulation suspension (breakpoints).

Figure 8-13. The SPI Priority Register (SPIPRI)5.

The SPI SUSP FREE and SPI SUSP SOFT bits. Bits 4 and 5 configure the behavior of the SPI upon emulation suspend: a) SOFT-FREE = 00 corresponds to immediate stop, b) SOFT-FREE = 10, forces a stop after the pending transmission/reception has been concluded and c) SOFTFREE = X1 continues operation of the SPI regardless of suspension. Typically, we prefer immediate stop (“00”) in our examples. The SPI PRIORITY bit. Bit 6 defines the priority level for the SPI interrupt. If “0”, SPI interrupts are high priority requests; if “1”, interrupts are low priority requests. In our example, we will be configuring SPI for low priority interrupt requests. 8.2.8 The SPI Receive and Transmit Buffer Registers (SPIRXBUF and SPITXBUF) The SPI receive buffer register (SPIRXBUF) and transmit buffer register (SPITXBUF) are the only 16-bit registers in the peripheral (since the length of a character can be configured up to 16 bits). The SPIRXBUF contains the character received; reading the SPIRXBUF clears the SPI interrupt flag. Accordingly, in order to transmit, the character should be written to SPITXBUF. If we write a character to SPITXBUF, the TXBUFFULL flag is set, until the character has been transmitted. 8.2.9 The SPI Emulation Buffer Register (SPIRXEMU) The SPI emulation buffer register (SPIRXEMU) is a 16-bit register containing a copy of the received data, used for debugging purposes. We may read SPIRXEMU should we wish to obtain the received data without clearing the SPI interrupt flag.

5

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

179

THE SERIAL PERIPHERAL INTERFACE

8.2.10 Initialization of the SPI Now that the SPI header file is ready, it is about time to write the SPI initialization code. Create a new C file named “SPIFunc.c” and add the following code: #include "DSP24_Spi.h" #include "DSP24_Core.h" void initSPI(void) { // 1. Configuring SPI Configuration Control register (SPICCR) SPICCRbits->bit.SPIRST = 0; // Reset SPI flags. Output inactive SPICCRbits->bit.CHARLEN = 15; // 16-bit character length SPICCRbits->bit.CLKPOL = 0; // data is output on rising edge // 2. Configuring Operation Control Register (SPICTL) SPICTLbits->bit.OVRNINTENA = 0; // disable receiver overrun SPICTLbits->bit.CLKPHSEL = 0; // Normal SPI Clock Phase SPICTLbits->bit.MST_SLV = 1; // Device is Master SPICTLbits->bit.TALK = 1; // Enable transmission SPICTLbits->bit.INTENA = 1; // Enable interrupts // 3. Baud rate register (SPIBRR) configuration *SPIBRR = 127; // minimum bps rate (314,959) for 40Mhz CLKOUT // 4. Configuring SPI Priority Control Register (SPIPRI) SPIPRIbits->bit.SPIPRI = 1; // Low priority interrupt requests SPIPRIbits->bit.SPISOFT = 0; SPIPRIbits->bit.SPIFREE = 0; // immediate stop on suspend //5. enabling interrupts *IFR |= 0x10; // clearing global int5 flag *IMR |= 0x10; // unmasking core interrupt 5 //6. enabling the spi SPICCRbits->bit.SPIRST = 1; }

Save the file and add it to the project. Much like in several cases of peripherals in previous examples, initialization of the SPI roughly includes the configuration of control and baud registers, as well as the enabling of the interrupts. Initially, the SPI flags are being cleared (SPIRST = 0). You may not have to do this, but it is generally a recommended action prior to configuring a peripheral. As a first step, the configuration control register (SPICCR) configures the SPI for 16-bit long characters (CHARLEN = 15) and clock polarity 0 (CLKPOL = 0). In the SPI operation control register we disable the receiver overrun interrupt (OVRINTENA = 0), select no phase for the clock (CLKPHSEL = 0), choose master mode for the device (MST_SLV = 1), enable the output line (TALK = 1) and enable the SPI interrupt (INTENA = 1). 180

THE SERIAL PERIPHERAL INTERFACE

As a third step, we choose the slowest possible option for the baud rate, which should correspond to a value of 127 in the BRR. The last step of the initialization sequence involves the configuration of the SPI priority register (SPIPRI): Interrupt priority is set to low (SPIPRI = 1) and the FREE-SOFT bits to “00” for immediate stop on emulation suspension. As always, it is imperative to enable the core interrupt (i.e., INT5) that “intercepts” the low priority SPI interrupt (Figure 8-14). High priority SPI interrupts correspond to INT1 core interrupt.

Figure 8-14. Core interrupt and peripheral interrupt vector for low priority SPI interrupts 6.

Figure 8-15. Core interrupt and peripheral interrupt vector for high priority SPI interrupts 6. 6

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

181

THE SERIAL PERIPHERAL INTERFACE

8.2.11 Sending a character To send a character, we simply need to place it in the transmit buffer (SPITXBUF). Expand file “SPIFunc.c” with the following function: void sendCharSPI(unsigned int ch) { // wait until transmit buffer is available while (SPISTSbits->bit.TxBUFFULL!=0); // place the character *SPITXBUF = ch; }

Save the changes. Notice the while-loop just before assigning the 16-bit character to the transmit buffer. The loop ensures that the new value is assigned to the buffer after the TXBUFFULL flag is cleared and therefore, there is no danger of overwriting any unsent data in the transmit buffer. 8.2.12 I/O Configuration The SPI requires four I/O pins: SPISIMO for input/output in slave/master mode, SPISOMI for output/input in slave/master mode, SPICLK for clocking and as the slave transmit enable signal. All four of these pins are multiplexed with general I/O pins 2, 3, 4 and 5 in port C, which are configured by I/O multiplexing register MCRB. Therefore, we need to add a definition for MCRB in “DSP24_Gpio.h”. Open the file and add the following definitions and external declarations: // MCRB struct MCRB_BITS { unsigned int WR_GPIOC0:1; // 0 PORTC0 if 0 unsigned int BIO_GPIOC1:1; // 1 PORTC1 if 0 unsigned int SPISIMO_GPIOC2:1; // 2 PORTC2 if 0 unsigned int SPISOMI_GPIOC3:1; // 3 PORTC3 if 0 unsigned int SPICLK_GPIOC4:1; // 4 PORTC4 if 0 unsigned int SPISTE_GPIOC5:1; // 5 PORTC5 if 0 unsigned int CANTX_GPIOC6:1; // 6 PORTC6 if 0 unsigned int CANRX_GPIOC7:1; // 7 PORTC7 if 0 unsigned int XINTADCSOC_GPIOD0:1; // 8 PORTD0 if 0/XINT or ADC SOC unsigned int EMU0_rsvd:1; // 9 reserved if 0/EMU0 unsigned int EMU1_rsvd:1; // 10 reserved if 0/ EMU1 unsigned int TCK_rsvd:1; // 11 reserved if 0/ TCK unsigned int TDI_rsvd:1; // 12 reserved if 0/TDI unsigned int TDO_rsvd:1; // 13 reserved if 0/TDO unsigned int TMS_rsvd:1; // 14 reserved if 0 / TMS unsigned int TMS2_rsvd:1; // 15 reserved if 0 / TMS2

182

THE SERIAL PERIPHERAL INTERFACE };

union MCRB_REG { unsigned int all; struct MCRB_BITS bit; }; extern volatile union MCRB_REG *MCRBbits;

Save the file. Now, open file “DSP24_Gpio.c” and add the declaration of MCRB: // MCRB declarations volatile union MCRB_REG *MCRBbits = (void*)0x7092;

If you haven’t done it so far, although not necessary for this example, now may be a good chance to add declarations and definitions for port B data and direction register (PBDATDIR) and port C data and direction register (PCDATDIR). If you are finished with the modifications in “DSP24_Gpio.h” and “DSP24_Gpio.c”, it’s time to modify the initIO() function in “DSP24_Sys.c”. Replace the old initIO() with the new code, shown below: void initIO(void) { // setting up SCIRXD and SCITXD for serial comms MCRBbits->bit.SPISIMO_GPIOC2 = 1; // Use the pin MCRBbits->bit.SPISOMI_GPIOC3 = 1; // Use the pin MCRBbits->bit.SPISTE_GPIOC5 = 1; // Use the pin MCRBbits->bit.SPICLK_GPIOC4 = 1; // Use the pin

as as as as

SPISIMO SPISOMI SPISTE SPICLK

}

8.2.13 The main() function The main function is very much similar to the one used in the previous chapter example (SCI). To test the SPI as a master, we will be constantly sending a character out on the SPISIMO pin. Create a new file named “spimain.c” and type the following: extern extern extern extern

initSystem(void); initIO(void); initSPI(void); sendCharSPI(unsigned int ch);

void main() { initSystem(); initIO(); initSPI(); // enable interrupts asm(" CLRC INTM");

183

THE SERIAL PERIPHERAL INTERFACE // main loop while (1) { sendCharSPI(0x007); } }

Save the file and add it to the project. Since you most probably do not have any SPI devices at the moment (or even if you do, just be patient and go through this example first), a good way of testing the functionality of our program is to simply short the SPISIMO and SPISOMI pins and examine the contents of SPIRXBUF after the code hits a breakpoint inside the SPI interrupt service routine. 8.2.14 SPI Interrupt Service Routines Since we configured the SPI for low priority interrupt requests, we should now modify the core INT5 interrupt service routine, in file “DSP24_DefaultISR.c”: Go to the GISR for INT5 and expand the switch statement as shown below: interrupt void INT5_GISR(void) { unsigned int peripheral; asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM"); switch (peripheral) { case 0x0006: // SCI Receive RXINTLP_ISR(); break; case 0x0007: // SCI Transmit TXINTLP_ISR(); break;

Added case branch for the SPI low priority ISR

case 0x0005: // SPI low priority interrupt SPIINTLP_ISR(); break; default: break; } // reset INT5 flag by writing a "1" in bit 4 of IFR *IFR|=0x10; }

Now we need to add function SPIINTLP_ISR() somewhere in the file (preferably, after the SCI routines):

184

THE SERIAL PERIPHERAL INTERFACE interrupt void SPIINTLP_ISR(void) { unsigned int SPIData; unsigned int flagOverrun; unsigned int flagTxBufFull; flagOverrun = SPISTSbits->bit.RxOVRN; flagTxBufFull = SPISTSbits->bit.TxBUFFULL; // read receive buffer, clear interrupt flag SPIData = *SPIRXBUF; asm(" NOP"); }

Do not forget that you have to make the appropriate additions to file “DSP24_DefaultISR.h” (i.e., declaration of SPIINTLP_ISR() and add an include statement for “DSP24_Spi.h”). Keep in mind that, unlike SCI, SPI transmissions occur simultaneously in both directions (i.e., from the master to the slave and from the slave to the master).Therefore, there is no need to use separate interrupts for reception and transmission. In any case, the interrupt flag must be cleared by reading the receive buffer, even if it does not contain valid data (i.e., the SPI line for data reception is not used). 8.2.15 Building and Executing If all went according to plan, your project window should like the one shown in Figure 8-16. Now, connect your DSP, do Rebuild All and load the program. Don’t run it just yet.

Figure 8-16. Project window for “SPI.pjt”.

185

THE SERIAL PERIPHERAL INTERFACE

We are going to be testing the SPI with a loop-back connection from SPISIMO to SPISOMI, therefore you should locate the pins on the DSP board (Figure 8-17) and use a jumper wire to connect them.

Figure 8-17. SPI pin locations on the DSP board I/O header7.

If the SPISOMI and SPISIMO are already connected, as a final step, go to the SPI ISR and place a breakpoint, as shown in Figure 8-18.

Figure 8-18. Breakpoint inside the SPI low priority ISR.

Now, go Debug and hit Run. If all went well, execution should suspend upon the breakpoint you just set and the content of variable SPIData should be equal to 7 (Figure 8-19).

7

eZdsp™ LF2407A– Technical Reference (Spectrum Digital)

186

THE SERIAL PERIPHERAL INTERFACE

Figure 8-19. Execution break inside the SPI low priority ISR.

8.3 Summary/Conclusions The SPI is a serial communications standard employed widely in commercial off-theshelf devices such as analog to digital converters, sensors, LCDs, EEPROMs, etc. Physical connections are minimal and configuration is relatively easy. Although you will not be using the SPI for DSP-PC communications (the SCI-UART is rather more appropriate), however, it is likely that you will have to use the SPI to control motor drivers, poll sensors or even write to LCDs in your application. Once again, it is recommended that you go through the relative chapter in the TMS320LF/LC240xA DSP Controllers Reference Guide - System and Peripherals and that you experiment with the various configurations. Additionally, it may be useful to track the , SPICLK and SPISOMI or SPISIMO lines with an oscilloscope for different values in clock polarity and phase.

187

THE SERIAL PERIPHERAL INTERFACE

188

THE CONTROLLER AREA NETWORK MODULE

9

The Controller Area Network (CAN) Module

9.1 Overview of the CAN Bus Protocol The controller area network, usually referred to as CAN or CAN bus protocol, is a simple broadcast protocol over a common channel (bus) shared by all connected nodes in the network (Figure 9-1). The CAN was originally designed for applications in the automotive industry as the means to accommodate information exchange between the microcontrollers installed in various locations on a car. Imagine that all these indications that you see (along with some that you don’t) on the dash board of your car are, in fact, remote sensors (engine, wheels, chassis, etc.) connected to microcontrollers which communicate with another microcontroller installed locally, responsible for displaying these indications on the board. The CAN is designed to accommodate the fast and reliable (in terms of electromagnetic interference) transmission of relatively short pieces of information (a few bytes), also known as CAN data frames, between embedded platforms located several meters apart from each other.

Figure 9-1. Generic layout of a CAN bus with n-nodes.

9.1.2 The CAN Physical Layer In terms of physical connections, the CAN bus is nothing but two wires, namely, CAN High (CANH) and CAN Low (CANL), connected to each node through a buffering circuit known as a CAN transceiver (Figure 9-3). Each node uses two separate lines for serial transmission (Tx) and reception (Rx) which cannot be directly connected to the CAN bus lines; instead, the 189

THE CONTROLLER AREA NETWORK MODULE

CAN transceiver takes care of the relative voltage conversions and signal routing/multiplexing required to interface the Tx and Rx lines to the CANH and CANL lines. Specifically, the DSP provides CANTX and CANRX pins multiplexed with IOPC6 and IOPC7 respectively, but it is up to you to interface those lines to the CAN bus by selecting and using the appropriate transceiver (Figure 9-3). 9.1.3 The CAN lines (CANH and CANL) The CAN bus is essentially a twisted pair of wires (CANH and CANL), each with a resistance of no more than 60 mΩ/m and terminator resistors usually chosen at 120 Ω. The CAN lines represent information based on a method known as differential signaling, according to which, the voltage difference between the CANH and CANL lines represents either a “0” or a “1”. Specifically, the CAN bus contents, henceforth called the state of the bus, are recognized according to the following rule:

From the above, it can be easily inferred that logic “1” is a common bus state even when nodes are not transmitting. Moreover, if two nodes decide to simultaneously transmit a “1” and a “0” respectively, then the bus will eventually have a “0” state, since the first node will be applying significant voltage to the bus, while the second one will be practically idle (or forcing a minor voltage difference). For this reason, logic “1” bus states are called recessive, whereas logic “0” bus states are called dominant. A great advantage of differential signaling is that the noise present in one line is moreless present in the second; therefore, by retrieving the voltage difference between the CANH and CANL lines, we “cancel-out” the common noise to a great extent. To demonstrate this with simple algebra, let NCOMMON be the common noise and VCANH and VCANL the noise-free voltages in CANH and CANL respectively; the voltage difference between the two lines can then be calculated as:

Although the actual equations tend to be rather more complicated, however, the overall principle is sane. The technique of canceling out common noise by retrieving voltage differences is called common noise rejection and it really serves the CAN bus nicely! In fact, the CAN bus is fairly reliable for distances up to 40 m with data transfer rates that may reach 1 Mbps. Figure 9-2 illustrates the general principle of common noise rejection in the CAN bus.

190

THE CONTROLLER AREA NETWORK MODULE

Figure 9-2. The general principle of common noise rejection in the CANH and CANL lines.

The CAN bus lines do not convey CMOS/TTL voltage level signals and moreover, the DSP uses two separate pins to transmit (CANTX) and receive (CANRX) data, whereas the CAN bus can be thought of as one single bi-directional data line for both transmission and reception. In fact, the CANH and CANL lines can reach up to 18 V with a voltage level of CANH – CANL = 18 V – 9V for dominant bits and CANH – CANL = 9 V – 18 V for recessive bits. For these reasons, a buffering circuit is required to interface the Rx and Tx lines with the bus. The LF2407A, as well as other commercial microcontrollers equipped with a CAN module, do not usually come with an on-board CAN transceiver; therefore, it is up to you to build the buffer. CAN transceivers are not difficult to interface and they are not exactly hard to find as there are several manufacturers of such integrated circuits. Figure 9-3 shows package and function diagram of the ISO1050 CAN transceiver by Texas Instruments.

Figure 9-3. Package outline and function diagram of TI’s ISO1050 CAN transceiver1.

9.1.3 Exchanging messages through the CAN bus using Data and Remote Request Frames The CAN bus protocol is designed to accommodate the broadcasting of data frames throughout the common bus. Each data frame contains an identifier (ID) field, based on which, 1

ISO1050 – ISOLATED CAN TRANSCEIVER (Texas Instruments)

191

THE CONTROLLER AREA NETWORK MODULE

the rest of the nodes decide on whether to keep it or ignore it. If a frame arrives to a node and the ID is not included in the list of acceptable IDs for that particular node, then the frame is ignored, otherwise the node either stores the frame data or automatically tries to respond to it by placing a new frame on the bus. To clarify the last statement, the CAN bus protocol involves essentially two different types of frames in terms of message exchanging: a) Data frames, containing data and b) Remote frames, requesting a reply from the nodes that accept their ID. The structure of a CAN data frame is shown in Figure 9-4.

Figure 9-4. Structure of a CAN frame.

The SOF field. Transmission of a frame always commences with a start of frame (SOF) bit which should be dominant (i.e., “0”). The Arbitration field. Based on the original CAN specification (2.0A, standard), the arbitration field is essentially the identifier of the frame, along with the remote request (RTR) bit; if the RTR bit is set, the frame does not contain data, but rather is a request for a data frame. Based on a newer CAN specification (2.0B, extended), the arbitration field is 32 bits long, containing two identifiers of length 11 (base ID) and 18 (extended ID), a flag that defines whether the extended or standard ID is used (IDE), the substitute remote request (SRR) bit which overrides the RTR bit, if using extended ID; finally, the RTR bit concludes the list. Practically, you shouldn’t be extremely worried about all these details, since the DSP CAN controller deals with the frame “wrapping” automatically. Your task is to configure the peripheral by defining the valid IDs and to provide or handle the frame data. However, knowing the mechanisms governing the exchange of data throughout the CAN bus will give you a better insight and intuition as to what may go wrong in case your program does not produce the results you expected. Note here that the DSP CAN controller uses the extended arbitration field structure with the option of using the standard by setting the IDE bit to “0”. Figures 9-5a and 9-5b illustrate the structure of a standard and extended CAN frame arbitration field.

Figure 9-5a. Standard CAN frame arbitration field structure.

192

THE CONTROLLER AREA NETWORK MODULE

Figure 9-5b. Extended CAN frame arbitration field structure.

The Control field. The control field is 6-bit long with the last four bits containing the data length code (DLC), which is the number of bytes contained in the data field. The first two bits in the control field are reserved and usually receive dominant values; generally, they are ignored. Figure 9-6 shows the structure of a CAN frame control field.

Figure 9-6. CAN frame control field structure.

The Data field. The CAN frame data field contains data which cannot exceed 8 bytes in length. The CRC field. The CRC field contains a 15-bit cyclic redundancy checksum sequence calculated based on BCH code, and a last bit, the CRC delimiter, which is always recessive. Figure 9-7 shows the structure of the CAN CRC field.

Figure 9-7. CAN frame CRC field structure.

The ACK field. The ACK field contains two bits, the ACK slot and the ACK delimiter. Initially, both bits are recessive in the frame upon transmission. Since the CAN bus is a closed channel, each node can “hear” itself while placing bits on the bus. Once a different node receives the frame data correctly, it then asserts a dominant bit during the time period that the transmitting node places the ACK slot bit on the bus. This way, the “echo” of the ACK slot bit comes back as dominant to the transmitting node, hence, interpreted as a sign that at least 193

THE CONTROLLER AREA NETWORK MODULE

one remote node has received the frame correctly. As far as the DSP is concerned, the ACK slot corresponds to a flag (ACK) and you may either choose to use it or ignore it. Figure 9-8 illustrates the CAN frame ACK field bits.

Figure 9-8. CAN frame ACK field structure.

The EOF field. The end of frame (EOF) field contains seven recessive bits. It should be noted that remote request frames are, in fact, data frames without the control and data fields. Consecutive frames placed on the bus are separated from each other by interframe space, which is composed of a series of 3 recessive bits called the intermission and a series of 8 recessive bits called the transmission suspend field. Essentially, the “hardcore” break between frames is the intermission, during which, none of the nodes can initiate transmission. The transmission suspend field is a period during which, error passive nodes (explained shortly) may not initiate transmission; however, others may do. Following transmission suspend, if no transmission has been initiated (with a dominant bit), the bus is said to be in idle state. Figure 9-9 shows the structure of the interframe space.

Figure 9-9. Interframe Space structure.

9.1.4 Placing frames on the CAN bus A CAN node wishing to place a frame on the bus has to wait for a sufficient amount of recessive bits (at least 3) after which, the bus is said to be in idle state. This way, the node ensures that transmission will not commence while some other node is still transmitting a frame. As soon as the bus is idle, the node begins transmission of the SOF bit followed by the frame’s ID. It is likely that several other nodes will decide on placing their data on the bus as well, by transmitting their SOF and ID. This is a situation in which there can be only one left.

194

THE CONTROLLER AREA NETWORK MODULE

Arbitration for control of the bus is done on the basis of which node ID will eventually get through to the bus. The idea is that, whoever puts a dominant bit (“0”) wins over the ones who put a recessive bit (“1”) and given that IDs should be unique, eventually only one ID will go through. As soon as a node wins arbitration, all other nodes resign and wait until the “winner” transmits its entire frame. Following transmission of the frame, the “losers” will attempt transmission and the process repeats itself, thus ensuring that, all in good time, everybody will transmit their frame. As an example, take three CAN nodes attempting to transmit frames with ID values of 7 (0111b), 5 (0101b) and 8 (1000b) and assume for the sake of simplicity, that the IDs are 4-bit long. Upon transmission of the first ID bit, nodes 1 and 2 win the bus, while the third resigns. Upon the second bit, both nodes win, since they are placing a “1” on the bus. However, on the third bit node 2 wins, since it places a dominant bit (“0”), whereas node 1 places a recessive (“1”) and therefore it resigns from the arbitration. Following the forfeit of nodes 1 and 3 from the arbitration, node 2 will transmit the remaining frame bits, while the others will sit and wait until the frame transmission is complete. Once transmission is complete, nodes 1 and 3 will compete again for the bus. Figure 9-10 shows the sequence of events during the arbitration.

Figure 9-10. Bus arbitration between three CAN nodes with 4-bit long IDs.

Except the arbitration rules mentioned above, there are several other details ensuring that there will be no collisions on the bus which can be found in the CAN Specification 2.0 manual by Robert Bosch and it is highly recommended that you read through the relative sections (if not all). The CAN controller of the DSP takes care of transmission and reception without you having to deal with any of the low level operations mentioned here; however, configuring the controller is your task and in order to that, you should be aware to some extent, of the events taking place on the CAN bus. 195

THE CONTROLLER AREA NETWORK MODULE

9.1.5 The Bit Stuffing Rule The CAN protocol allows only up to five consecutive bits of the same value on the bus. This means that, if, for example, a data field contains five consecutive “0”s, the transmitter will have to “stuff” an additional bit complementary to the previous five (i.e., a “1”). This is happening because during the transmission of “0”s, nodes may lose synchronization and therefore, take advantage of the change in the state of the bus (i.e., “0” to “1”) as a milestone based on which they may re-synchronize. Figure 9-11 illustrates examples of bit stuffing for five consecutive bits of the same polarity.

Figure 9-11. Bit stuffing after five consecutive dominant bits.

9.1.6 Error and Overload Frames The CAN protocol involves two other types of frames, not used for data exchange, but rather as error signals on the bus. Specifically, the protocol also includes error frames and overload frames. Error frames are created following an error in a node and can be either active or passive. In the case of an error, any node can force “unnatural” situations to occur on the bus (such as 6 consecutive dominant bits, or 6 consecutive recessive bits breaking the bit stuffing rule), perceived as errors by all nodes. Error frames are visible only in the transfer layer (and therefore handled solely by the CAN controller), but they will affect flags and may even cause an interrupt which you may have to deal with, given the specifics of the application at hand. Overload frames are used to provide delays between succeeding or preceding data frames or remote requests. Much like in error frames, they cause situations incompatible to the bit stuffing rule, thus forcing the nodes to respond accordingly. Simply put, overload frames can be used by any node to delay the others from placing data frames or remote requests on the bus. 9.1.7 States of a CAN node A CAN node switches states according to the number of errors in reception and transmission, stored in separate counters (receive error counter and transmit error counter). 196

THE CONTROLLER AREA NETWORK MODULE

Upon initialization, the node enters an error active state. The node may switch from error active state to an error passive state, if the number of reception or transmission errors exceeds 127. Note here that the receive error and transmit error counters will decrease upon successful receptions/transmissions and therefore, it is possible for the node to switch back to the error active state. If the transmit errors exceed 255, the node will switch to the bus-off state upon which, the CAN module is disconnected from the bus by setting its lines in high impedance. The node cannot return to passive error state from the bus-off state; it can only switch to the initial state (error active) following a module reset. Figure 9-12 shows the state transition diagram of a CAN node. The state of the node defines the course of action taken by the CAN module upon error detection. Nodes in active error state will produce active error flags, whereas nodes in passive error state produce passive error flags. In general, if a node detects an error on the bus, it will attempt to replace the content of the bus with an error frame (Figure 9-13). The error frame is composed of two fields: a) The error flag field, having a length of 6-12 bits and b) The error delimiter field, composed of 8 consecutive recessive bits. The general idea behind error frames is that, upon a bus error, every error active/passive node will attempt – with a certain phase shift from each other - to transmit an error frame of some sort. This will result in having a series of superimposed bits of varying length (6-12 bits), followed by a series of 8 recessive bits.

Figure 9-12. CAN node state transition diagram.

197

THE CONTROLLER AREA NETWORK MODULE

Figure 9-13. Error frame structure.

The possible behavior of a CAN node upon error detection can be either of the following: Error transmission by an Error Active node. If an error active node “senses” an error on the bus, it will attempt to place 6 consecutive dominant bits (error flag) and 8 consecutive recessive bits (error delimiter). Error Transmission by an Error Passive node. If an error passive node “senses” an error, it will transmit an error frame with a flag of 6 recessive bits. Let take an example of a CAN bus with three nodes. Assume that nodes 1 and 2 are in error active state, while the third is in error passive state; assume now, that at some point, node 1 detects an error on the bus. Following the error detection, node 1 will place 6 consecutive “0”s and 8 consecutive “1”s. Node 2 will either detect the error shortly afterwards (or simply perceive the error frame placed by node 1 as an error) and also place an active error frame on the bus. Node 3 will also “see” an error sometime after node 1 or 2 and will wait until 6 consecutive bits are detected on the bus. A possible scenario for the resulting frame on the bus could be the one shown in Figure 9-14.

198

THE CONTROLLER AREA NETWORK MODULE

Figure 9-14. Superimposed error frames by two error active nodes and one error passive node.

9.1.8 Bit Timing and Synchronization The CAN protocol uses a minimum time interval called the time quantum (TQ), based on which, every other time period is built and measured. Specifically, the time period required for the transmission of a bit is divided into time segments measured in TQ. Obviously, TQ is configurable as a scaled version of the node’s CPU clock period and should be equal for every node in the network. The transmission time of a bit can vary from 8 to 25 TQ. Specifically, bit period is divided into the following four segments measured in TQ: SYNC_SEG (Synchronization Segment. It is 1 TQ long, used to synchronize the nodes. PROP_SEG (Propagation Time Segment). It can be 1, …, 8 TQ long. It is used to compensate for signal delays across the network. PHASE_SEG1 (Phase Buffer Segment 1). It can be 1, …, 8 TQ long. It is used to compensate for re-synchronization (occurs at the beginning of the transmission of a bit) errors between nodes. PHASE_SEG2 (Phase Buffer Segment 2). It can be 1, .., 8 TQ long and it is also used to compensate for phase errors. The bit period cannot exceed 25 TQ and can be no less than 8 TQ. Note that at the end of PHASE_SEG1 and beginning of PHASE_SEG2, the bit is sampled by the nodes. Figure 9-15 shows the time segments to which the bit period is divided.

199

THE CONTROLLER AREA NETWORK MODULE

Figure 9-15. Bit period time subdivisions.

The CAN nodes may occasionally synchronize themselves with the others by monitoring transitions in the bus state (dominant to recessive or recessive to dominant). Specifically, there are two types of synchronization employed: Hard synchronization and resynchronization. Hard Synchronization. A hard synchronization is performed whenever a transition from recessive to dominant is detected on the bus while in idle state (in other words, a start of frame). During hard synchronization, all nodes that monitor the presence of the dominant bit (except the sender) will attempt to adjust their internal bit timing mechanisms in a way such as to place the “edge” (transition) time inside their synchronization segment (SYNC_SEG). Re-synchronization. Re-synchronization is performed by a node if a transition (recessive to dominant or dominant to recessive) appears “misplaced” somewhere out of the boundaries of the synchronization segment as perceived by that particular node. Depending on the phase (positive or negative), the node will attempt to extend the length of PHASE_SEG1 (at the expense of PHASE_SEG2) or extend the length of PHASE_SEG2 (at the expense of PHASE_SEG1) by a number of 1-4 TQ, defined by the synchronization jump width. The synchronization jump width (SJW) is the maximum number of time quanta by which a node may extend the PHASE_SEG1 or PHASE_SEG2 interval during re-synchronization.

9.2 Overview of the CAN Controller The CAN controller is yet another highly configurable peripheral on the LF2407A. It supports extended data frames and implements the low level functions required to receive and transmit messages over the CAN bus, leaving us with the task of configuring the identifiers and mapping them to specific memory locations and registers. Based on this configuration, the CAN module will transmit and/or receive data over the bus. Figure 9-16 shows the CAN module block diagram.

200

THE CONTROLLER AREA NETWORK MODULE

Figure 9-16. Block diagram of the CAN controller2.

9.2.1 CAN Mailboxes The CAN controller uses a set of memory locations called mailboxes, to store data either received, or pending transmission from/to the bus. Each mailbox can be configured as either receive or transmit. The controller relates the mailbox with an identifier (or a mask) in order to handle incoming or outgoing data and remote request frames. Specifically, if a mailbox is configured as receive and an incoming data frame matches its ID (or ID acceptance mask), the CAN controller will automatically load the mailbox with the data field of the incoming frame. In quite a similar way, if a mailbox is configured as transmit, then, upon transmission, the contents of the mailbox are copied into a new data frame using the ID of the mailbox. Transmit mailboxes can be optionally configured to automatically respond to remote requests. The CAN controller provides six mailboxes (0-5) stored in consecutive memory locations, along with a set of configuration registers for direction (receive/transmit), activation, identifier and flags. Mailboxes 0 and 1 can only be enabled as receive, while mailboxes 4 and 5 can only be enabled as transmit. Mailboxes 2 and 3 can be either receive or transmit.

2

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

201

THE CONTROLLER AREA NETWORK MODULE

Figure 9-17. The LF2407A CAN module memory space3.

To summarize, the idea is fairly simple: The CAN controller receives data frames and checks if there is a mailbox with a matching ID; if so, the data field of the frame is copied into the mailbox and a flag is raised (receive interrupt), otherwise the frame is discarded. In quite the same way, if the frame is a remote request, the CAN locates the transmit mailbox with a matching ID and initiates transmission, if the auto-reply option is enabled; otherwise, it ignores the frame. Transmission can be initiated at will and the rest is up to the controller; however, there are several flags to keep track of, in order to ensure that the frame was received by the recipients. A transmit interrupt can be generated by the TAx flag (related to the ACK field) which signifies that at least one node received the message correctly. Figure 917 illustrates the LF2407A CAN memory map with the locations of the six mailboxes and the relative registers.

9.3 Using the CAN module to Transmit and Receive Data frames Unfortunately, the CAN module is fairly extensive in terms of the relative control, data and flag registers and therefore, there is a lot of preliminary work to be done to define and declare all relative structures. You may choose to type only the registers involved in our example, but it would be advisable to type the full code, since most of the registers are declarations of the very same data structure. In the example that follows, we will configure 3

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

202

THE CONTROLLER AREA NETWORK MODULE

the CAN module to use mailbox 0 for message reception and mailbox 2 for transmission. Since, presumably, you don’t have a CAN transceiver or a second DSP to create a CAN network with at least two nodes, it will be fairly difficult to test the program using only the CANRX and CANTX pins as we did in previous examples (SCI, SPI); however, we may be able to emulate certain erroneous bus conditions by shorting the CANRX and CANTX pins and monitoring the behavior of the CAN module during transmission. 9.3.1 Creating a new header file for the CAN registers Create a new folder named “CAN” and copy files “DSP24_Core.h”, “DSP24_Core.c”, “DSP24_Gpio.h”, “DSP24_Gpio.c”, “DSP24_Sys.c”, “DSP24_DefaultISR.h”, “DSP24_DefaultISR.c”, “cvectors.asm” and “2407_cmd.cmd” from our previous example folder. Now, create a new project named “CAN” and add the files to it, along with “rts2xx.lib”. As a next step, we need to define a set of structures for the registers of the CAN module and declare the relative pointers. Create a new header file named “DSP24_Can.h” and type-in the following code covering all registers in the LF2407A CAN module. You may also choose to type only the highlighted structures: #ifndef DSP24_CAN_H #define DSP24_CAN_H // ***************** Message ID Registers *************************** // Mailbox 0 Low ID register (MSGID0L) extern volatile unsigned int *MSGID0L;

Mailbox 0 Low ID register

// Mailbox High ID register declaration struct MSGIDnH_BITS { unsigned int IDH:13; // high ID 13 bits unsigned int AAM:1; // Auto Answer Mode unsigned int AME:1; // Acceptance Mask Enable unsigned int IDE:1; // Identifier extension };

Mailbox-n High ID definition

union MSGIDnH_REG { unsigned int all; struct MSGIDnH_BITS bit; }; // Mailbox 0 High ID register extern volatile union MSGIDnH_REG *MSGID0Hbits; // Mailbox 1 Low ID register (MSGID1L) extern volatile unsigned int *MSGID1L;

Mailbox-0 High ID declaration

// mailbox 1 High ID register extern volatile union MSGIDnH_REG *MSGID1Hbits;

203

THE CONTROLLER AREA NETWORK MODULE

// Mailbox 2 Low ID register (MSGID2L) extern volatile unsigned int *MSGID2L; // Mailbox 2 High ID register extern volatile union MSGIDnH_REG *MSGID2Hbits; // Mailbox 3 Low ID register (MSGID3L) extern volatile unsigned int *MSGID3L;

Mailbox-2 Low and High ID declaration

// Mailbox 3 High ID register (MSGID3H) extern volatile union MSGIDnH_REG *MSGID3Hbits; // Mailbox 4 Low ID register (MSGID4L) extern volatile unsigned int *MSGID4L; // Mailbox 4 High ID register extern volatile union MSGIDnH_REG *MSGID4Hbits; // Mailbox 5 Low ID register (MSGID5L) extern volatile unsigned int *MSGID5L; // Mailbox 5 High ID register extern volatile union MSGIDnH_REG *MSGID5Hbits;

// ***************** Message ID Registers ends *********************** // ******************** CAN Message Control Fields ********************** // Message Control Field generic (MSGCTRLn) struct MSGCTRLn_BITS { unsigned int DLC:4; // 0-3 Data length unsigned int RTR:1; // Remote Transmission Request unsigned int rsvd:11; };

Message Control Field bitwise definition

union MSGCTRLn_REG { unsigned int all; struct MSGCTRLn_BITS bit; }; // MSGCTRL0 extern volatile union MSGCTRLn_REG *MSGCTRL0bits; // MSGCTRL1 extern volatile union MSGCTRLn_REG *MSGCTRL1bits; // MSGCTRL2 extern volatile union MSGCTRLn_REG *MSGCTRL2bits; // MSGCTRL3 extern volatile union MSGCTRLn_REG *MSGCTRL3bits;

Mailbox 0 Message Control Field declaration Mailbox 2 Message Control Field declaration

// MSGCTRL4 extern volatile union MSGCTRLn_REG *MSGCTRL4bits; // MSGCTRL5 extern volatile union MSGCTRLn_REG *MSGCTRL5bits;

204

THE CONTROLLER AREA NETWORK MODULE

// ****************** Message Control Bits ends *********************

// Mailbox Direction/Enable Register // MDER struct MDER_BITS { unsigned int ME0:1; // 0 Enable Mailbox 0 unsigned int ME1:1; // 1 Enable Mailbox 1 unsigned int ME2:1; // 2 Enable Mailbox 2 unsigned int ME3:1; // 3 Enable Mailbox 3 unsigned int ME4:1; // 4 Enable Mailbox 4 unsigned int ME5:1; // 5 Enable Mailbox 5 unsigned int MD2:1; // 6 Mailbox 2 Direction unsigned int MD3:1; // 7 Mailbox 3 Direction unsigned int resvd:8; // 8-15 reserved }; union MDER_REG { unsigned int all; struct MDER_BITS bit; }; extern volatile union MDER_REG *MDERbits;

Mailbox Direction / Enable Register definition and declaration

Transmission Control Register definition and declaration

// Transmission Control Register // TCR struct TCR_BITS { unsigned int TRR2:1; //0 Transmission Request for Mailbox 2 unsigned int TRR3:1; //1 Transmission Request for Mailbox 3 unsigned int TRR4:1; //2 Transmission Request for Mailbox 4 unsigned int TRR5:1; //3 Transmission Request for Mailbox 4 unsigned int TRS2:1; //4 Transmission Request for Mailbox 2 unsigned int TRS3:1; //5 Transmission Request for Mailbox 3 unsigned int TRS4:1; //6 Transmission Request for Mailbox 4 unsigned int TRS5:1; //7 Transmission Request for Mailbox 5 unsigned int AA2:1; //8 Abort Acknowledge bit for Mailbox 2 unsigned int AA3:1; //9 Abort Acknowledge bit for Mailbox 3 unsigned int AA4:1; //10 Abort Acknowledge bit for Mailbox 4 unsigned int AA5:1; //11 Abort Acknowledge bit for Mailbox 5 unsigned int TA2:1; //12 Transmission Aknowledge for Mailbox unsigned int TA3:1; //13 Transmission Aknowledge for Mailbox unsigned int TA4:1; //14 Transmission Aknowledge for Mailbox unsigned int TA5:1; //15 Transmission Aknowledge for Mailbox };

2 3 4 5

union TCR_REG { unsigned int all; struct TCR_BITS bit; }; extern volatile union TCR_REG *TCRbits;

205

THE CONTROLLER AREA NETWORK MODULE

Receive Control Register definition and declaration

//CAN Receive Control Register // RCR struct RCR_BITS { unsigned int OPC0:1; // 0 Overright Protection Control for MB unsigned int OPC1:1; // 1 Overright Protection Control for MB unsigned int OPC2:1; // 2 Overright Protection Control for MB unsigned int OPC3:1; // 3 Overright Protection Control for MB unsigned int RMP0:1; // 4 Received Message Pending for MB 0 unsigned int RMP1:1; // 5 Received Message Pending for MB 1 unsigned int RMP2:1; // 6 Received Message Pending for MB 2 unsigned int RMP3:1; // 7 Received Message Pending for MB 3 unsigned int RML0:1; // 8 Received Message Lost for MB 0 unsigned int RML1:1; // 9 Received Message Lost for MB 1 unsigned int RML2:1; // 10 Received Message Lost for MB 2 unsigned int RML3:1; // 11 Received Message Lost for MB 3 unsigned int RFP0:1; // 12 Remote Frame Pending for MB 0 unsigned int RFP1:1; // 13 Remote Frame Pending for MB 1 unsigned int RFP2:1; // 14 Remote Frame Pending for MB 2 unsigned int RFP3:1; // 12 Remote Frame Pending for MB 3 }; union RCR_REG { unsigned int all; struct RCR_BITS bit; }; extern volatile union RCR_REG *RCRbits; // CAN Master Control Register // MCR struct MCR_BITS { unsigned int MBNR:2; // unsigned int rsvd1:4; // unsigned int STM:1; // unsigned int ABO:1; // unsigned int CDR:1; // unsigned int WUBA:1; // unsigned int DBO:1; // unsigned int PDR:1; // unsigned int CCR:1; // unsigned int SUSP:1; // unsigned int rsvd2:2; // };

0 1 2 3

Master Control Register definition and declaration

0-1 Mailbox number 2-5 reserved 6 Self Test Mode 7 AutoBus ON 8 Change Data field request(MB 2-3 only) 9 Wake Up on Bus Activity 10 Data Byte Order 11 Power Down Mode Request 12 Change Configuration Request 13 Action on Emulation suspend 14-15 reserved

union MCR_REG { unsigned int all; struct MCR_BITS bit; }; extern volatile union MCR_REG *MCRbits;

206

THE CONTROLLER AREA NETWORK MODULE

//Bit Configuration Registers // BCR2 struct BCR2_BITS { unsigned int BAUDPSC:8; // 0-7 Baud Rate prescaler unsigned int rsvd:8; // reserved }; union BCR2_REG { unsigned int all; struct BCR2_BITS bit; }; extern volatile union BCR2_REG *BCR2bits;

Bit Configuration Registers 1 and 2 definition and declaration

// BCR1 struct BCR1_BITS { unsigned int TSEG2:2; // 0-2 Time segment 2. PhaseSEG2 length in TQs unsigned int TSEG1:4; // 3-6 Time segment 1. PhaseSEG1 length in TQs unsigned int SAM:1; // 7 sample point setting unsigned int SJW:2; // 8-9 Synchronization Jump width unsigned int SBG:1; // 10 Synchronization on both edges unsigned int rsvd:5; // 11-15 reserved }; union BCR1_REG { unsigned int all; struct BCR1_BITS bit; }; extern volatile union BCR1_REG *BCR1bits; // CAN Error Status Register // ESR struct ESR_BITS { unsigned int WS:1; // 0 Warning Status unsigned int EP:1; // 1 Error Passive status unsigned int BO:1; // 2 Buss Off status unsigned int ACKE:1; // 3 ACKnowledge error unsigned int SER:1; // 4 Stuff error unsigned int CRCE:1; //5 CRC Error unsigned int SA1:1; // 6 Stuck at Dominant error unsigned int BEF:1; // 7 Bit error Flag unsigned int FER:1; // 8 Form error flag unsigned int rsvd:7; // 9-15 reserved }; union ESR_REG { unsigned int all; struct ESR_BITS bit; }; extern volatile union ESR_REG *ESRbits;

207

THE CONTROLLER AREA NETWORK MODULE // CAN Global Status Register // GSR struct GSR_BITS { unsigned int TM:1; // 0 Transmit mode unsigned int RM:1; // 1 Receive Mode unsigned int rsvd1:1; // 2 reserved unsigned int PDA:1; // 3 Power Down mode Acknowledge unsigned int CCE:1; // 4 Change Configuration Enable unsigned int SMA:1; // 5 Suspend Mode Acknowledge unsigned int rsvd2:9; // 6-15 reserved }; union GSR_REG { unsigned int all; struct GSR_BITS bit; }; extern volatile union GSR_REG *GSRbits; // CAN Error Counter register // CEC struct CEC_BITS { unsigned int REC:8; // 0-7 Receive error counter unsigned int TEC:8; // 8-15 Transmit error Counter }; union CEC_REG { unsigned int all; struct CEC_BITS bit; }; extern volatile union CEC_REG *CECbits;

CAN Interrupt Flag Register definition and declaration

// CAN Interrupt Flag Register // CAN_IFR struct CAN_IFR_BITS { unsigned int WLIF:1; // 0 Warning Level Interrupt unsigned int EPIF:1; // 1 Error Passive Interrupt Flag unsigned int BOIF:1; // 2 Buss Off Interrupt Flag unsigned int WUIF:1; // 3 Wake up Interript Flag unsigned int WDIF:1; // 4 Write Denied Interrupt Flag unsigned int AAIF:1; // 5 Abort Acknowledge Interrupt Flag unsigned int RMLIF:1; // 6 Receive Message Lost interrupt flag unsigned int rsvd1:1; // 7 reserved unsigned int MIF0:1; // 8 Mailbox 0 interrupt unsigned int MIF1:1; // 9 Mailbox 1 interrupt flag unsigned int MIF2:1; // 10 Mailbox 2 interrupt flag unsigned int MIF3:1; // 11 Mailbox 3 interrupt flag unsigned int MIF4:1; // 12 Mailbox 4 interrupt flag unsigned int MIF5:1; // 13 Mailbox 5 interrupt flag unsigned int rsvd2:2; // 14-15 reserved }; union CAN_IFR_REG { unsigned int all; struct CAN_IFR_BITS bit; }; extern volatile union CAN_IFR_REG *CAN_IFRbits;

208

THE CONTROLLER AREA NETWORK MODULE

CAN Interrupt Mask Register definition and declaration

// CAN Interrupt Mask Register // CAN_IMR struct CAN_IMR_BITS { unsigned int WLIM:1; // 0 Warning Level interrupt Mask unsigned int EPIM:1; // 1 Error Passive Interrupt Mask unsigned int BOIM:1; // 2 Buss off Interrupt Mask unsigned int WUIM:1; // 3 Wake up Interrupt Flag unsigned int WDIM:1; // 4 Write denied interrupt mask unsigned int AAIM:1; // 5 Abort Acknowledge interrupt mask unsigned int RMLIM:1; // 6 Receive Message Lost interrupt mask unsigned int EIL:1; // 7 Error Interrupt Priority Level unsigned int MIM0:1; // 8 Mailbox 0 Interrupt Mask unsigned int MIM1:1; // 9 Mailbox 1 Interrupt Mask unsigned int MIM2:1; // 10 Mailbox 2 Interrupt Mask unsigned int MIM3:1; // 11 Mailbox 3 Interrupt Mask unsigned int MIM4:1; // 12 Mailbox 4 Interrupt Mask unsigned int MIM5:1; // 13 Mailbox 5 Interrupt Mask unsigned int rsvd:1; // 14 reserved unsigned int MIL:1; // Mailbox Interrupt Priority Level }; union CAN_IMR_REG { unsigned int all; struct CAN_IMR_BITS bit; }; extern volatile union CAN_IMR_REG *CAN_IMRbits; // Local Acceptance Mask register high word (LAMn_H) struct LAMn_H_BITS { unsigned int LAM:13; // LAM Mask unsigned int rsvd:2; // reserved unsigned int LAMI:1; // LAM id extension bit }; union LAMn_H_REG { unsigned int all; struct LAMn_H_BITS bit; }; // Local Acceptance Mask 0 register high word (LAM0_H) // for mboxes 2 and 3 extern volatile union LAMn_H_REG *LAM0_Hbits;

Local Acceptance Masks 0 and 1 definition and declaration

// Local Acceptance Mask 0 register low word (LAM0_L) extern volatile unsigned int *LAM0_L; // Local Acceptance Mask 1 register high word (LAM1_H) // for mboxes 2 and 3 extern volatile union LAMn_H_REG *LAM1_Hbits; // Local Acceptance Mask 1 register low word (LAM1_L) extern volatile unsigned int *LAM1_L;

209

THE CONTROLLER AREA NETWORK MODULE

// Message Buffers // Mailbox 0 buffers A, B, C extern volatile unsigned int extern volatile unsigned int extern volatile unsigned int extern volatile unsigned int

and D *MBX0A; *MBX0B; *MBX0C; *MBX0D;

// Mailbox 1 buffers A, B, C extern volatile unsigned int extern volatile unsigned int extern volatile unsigned int extern volatile unsigned int

and D *MBX1A; *MBX1B; *MBX1C; *MBX1D;

// Mailbox 2 buffers A, B, C extern volatile unsigned int extern volatile unsigned int extern volatile unsigned int extern volatile unsigned int

and D *MBX2A; *MBX2B; *MBX2C; *MBX2D;

// Mailbox 3 buffers A, B, C extern volatile unsigned int extern volatile unsigned int extern volatile unsigned int extern volatile unsigned int

and D *MBX3A; *MBX3B; *MBX3C; *MBX3D;

// Mailbox 4 buffers A, B, C extern volatile unsigned int extern volatile unsigned int extern volatile unsigned int extern volatile unsigned int

and D *MBX4A; *MBX4B; *MBX4C; *MBX4D;

// Mailbox 5 buffers A, B, C extern volatile unsigned int extern volatile unsigned int extern volatile unsigned int extern volatile unsigned int

and D *MBX5A; *MBX5B; *MBX5C; *MBX5D;

#endif

Mailbox 0 Message Buffers declaration

Mailbox 2 Message Buffers declaration

// end

If you typed the code, save the file and take a short break. We now need the C file with the appropriate declarations of pointers to the memory addresses of the CAN module registers. Create a C file named “DSP24_Can.c” and type either the entire code that follows, or the highlighted declarations: #include "DSP24_Can.h" // MDER volatile union MDER_REG *MDERbits = (void*)0x7100; // TCR volatile union TCR_REG *TCRbits = (void*)0x7101;

210

THE CONTROLLER AREA NETWORK MODULE // RCR volatile union RCR_REG *RCRbits = (void*)0x7102; // MCR volatile union MCR_REG *MCRbits = (void*)0x7103; // BCR2 volatile union BCR2_REG *BCR2bits = (void*)0x7104; // BCR1 volatile union BCR1_REG *BCR1bits = (void*)0x7105; // ESR volatile union ESR_REG *ESRbits = (void*)0x7106; // GSR volatile union GSR_REG *GSRbits = (void*)0x7107; // CEC volatile union CEC_REG *CECbits = (void*)0x7108; // CAN_IFR volatile union CAN_IFR_REG *CAN_IFRbits = (void*)0x7109; // CAN_IMR volatile union CAN_IMR_REG *CAN_IMRbits = (void*)0x710A; // Local Acceptance Mask Registers // LAM0_H volatile union LAM0_H_REG *LAMn_Hbits = (void*)0x710B; // LAM0_L volatile unsigned int *LAM0_L = (void*)0x710C; // LAM1_H volatile union LAM1_H_REG *LAMn_Hbits = (void*)0x710D; // LAM1_L volatile unsigned int *LAM1_L = (void*)0x710E; // ************ Mailbox ID Registers ********* //MSGID0L volatile unsigned int *MSGID0L = (void*)0x7200; // MSGID0H volatile union MSGIDnH_REG *MSGID0Hbits = (void*)0x7201; //MSGID1L volatile unsigned int *MSGID1L = (void*)0x7208; // MSGID1H volatile union MSGIDnH_REG *MSGID1Hbits = (void*)0x7209; //MSGID2L volatile unsigned int *MSGID2L = (void*)0x7210; // MSGID2H volatile union MSGIDnH_REG *MSGID2Hbits = (void*)0x7211; //MSGID3L volatile unsigned int *MSGID3L = (void*)0x7218; // MSGID3H volatile union MSGIDnH_REG *MSGID3Hbits = (void*)0x7219; //MSGID4L volatile unsigned int *MSGID4L = (void*)0x7220;

211

THE CONTROLLER AREA NETWORK MODULE // MSGID4H volatile union MSGIDnH_REG *MSGID4Hbits = (void*)0x7221; //MSGID5L volatile unsigned int *MSGID5L = (void*)0x7228; // MSGID5H volatile union MSGIDnH_REG *MSGID5Hbits = (void*)0x7229; // MSGCTRL0 volatile union MSGCTRLn_REG *MSGCTRL0bits = (void*)0x7202; // MSGCTRL1 volatile union MSGCTRLn_REG *MSGCTRL1bits = (void*)0x720A; // MSGCTRL2 volatile union MSGCTRLn_REG *MSGCTRL2bits = (void*)0x7212; // MSGCTRL3 volatile union MSGCTRLn_REG *MSGCTRL3bits = (void*)0x721A; // MSGCTRL4 volatile union MSGCTRLn_REG *MSGCTRL4bits = (void*)0x7222; // MSGCTRL5 volatile union MSGCTRLn_REG *MSGCTRL5bits = (void*)0x722A; // MB0A,B,C,D volatile unsigned volatile unsigned volatile unsigned volatile unsigned

int int int int

*MBX0A *MBX0B *MBX0C *MBX0D

= = = =

(void*)0x7204; (void*)0x7205; (void*)0x7206; (void*)0x7207;

// MB1A,B,C,D volatile unsigned volatile unsigned volatile unsigned volatile unsigned

int int int int

*MBX1A *MBX1B *MBX1C *MBX1D

= = = =

(void*)0x720C; (void*)0x720D; (void*)0x720E; (void*)0x720F;

// MB2A,B,C,D volatile unsigned volatile unsigned volatile unsigned volatile unsigned

int int int int

*MBX2A *MBX2B *MBX2C *MBX2D

= = = =

(void*)0x7214; (void*)0x7215; (void*)0x7216; (void*)0x7217;

// MB3 A,B,C,D volatile unsigned volatile unsigned volatile unsigned volatile unsigned

int int int int

*MBX3A *MBX3B *MBX3C *MBX3D

= = = =

(void*)0x721C; (void*)0x721D; (void*)0x721E; (void*)0x721F;

// MB4 A,B,C,D volatile unsigned volatile unsigned volatile unsigned volatile unsigned

int int int int

*MBX4A *MBX4B *MBX4C *MBX4D

= = = =

(void*)0x7224; (void*)0x7225; (void*)0x7226; (void*)0x7227;

212

THE CONTROLLER AREA NETWORK MODULE // MB5 A,B,C,D volatile unsigned volatile unsigned volatile unsigned volatile unsigned

int int int int

*MBX5A *MBX5B *MBX5C *MBX5D

= = = =

(void*)0x722C; (void*)0x722D; (void*)0x722E; (void*)0x722F;

Save the changes you made and add the file to the project. If you typed all the header files in the previous chapters, then you have practically covered 90% of all peripherals on the LF2407A! In fact, if you complete files “DSP24_Gpio.h” and “DSP24_Ev.h”, you can call it a full library. Okay, unfortunately, break is over. We now have bitwise access to the CAN registers required to configure mailbox 0 for data frame reception and mailbox 2 for data frame transmission. At this point, before moving on to creating the CAN initialization routine, we should go through the bitwise description of the registers we have just included in our header file. 9.3.2 Message Identifier for High-Word Mailboxes 0-5 (MSGIDnH) Each of the six mailboxes has its own message identifier register (MSGID0H, …, MSGID5H). Recall that the DSP CAN module uses extended arbitration fields which can store either standard (11 bits) or extended (29 bits) CAN identifiers according to configuration. In any case, the MSGIDnH register stores the high-word of the mailbox identifier. Note that you should disable the mailbox from the mailbox direction/enable register (MDER) before configuring the corresponding ID registers.

Figure 9-18. Message Identifier for High-Word Mailboxes 0-5 (MSGIDnH)4.

The IDH bits. Bits 0-12 are the highest 13 bits of the extended identifier. If using standard identifier, then it should be stored in bits 2-12 of IDH. The AAM bit. Bit 13 is the auto answer mode bit. If the mailbox is receive, the bit has no effect. However, if the mailbox is configured as transmit, then a value of “1” causes the CAN module to answer to a remote request frame with a matching ID, whereas a value of “0”

4

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

213

THE CONTROLLER AREA NETWORK MODULE

disables the auto answer mode and the CAN module ignores the remote request frame that matches its ID (or ID mask). The AME bit. Bit 14 is the acceptance mask enable bit. If this bit is set to “1”, the mailbox matches identifiers of incoming frames using a mask stored in the corresponding local acceptance mask register (LAM); if “0”, then the acceptance mask is disabled and the mailbox uses exact ID matching. The IDE bit. Bit 15 is the identifier extension bit. If “0”, the mailbox is using standard ID (11 bits), whereas if “1”, extended (29 bits). 9.3.3 Message Identifier for Low-Word Mailboxes 0-5 (MSGIDnL) The Message Identifier for Low-Word (MSGID0L, …, MSGID5L) stores the low 16 bits of a mailbox ID. 9.3.4 Message Control Field (MSGCTRLn) The message control field (MSGCTRLn) contains the type of a mailbox frame (remote request or data) and the length of its data field. In order to make changes to MSGCTRLn, the mailbox must be disabled.

Figure 9-19. Message Control Field (MSGCTRLn)5.

The DLC bits. Bits 0-3 configure the length of the message data field (code data length) with values from 1 to 8. The RTR bit. Bit 4 is the remote transmission request bit. If RTR is “0”, the mailbox contains a message which corresponds to a data frame; if “1” the mailbox contains a message corresponding to a remote request frame. 9.3.5 The Local Acceptance Mask Register 0 and 1 High Word (LAM0_H and LAM1_H) Receive mailboxes 0, 1 and 2, 3 (if configured as receive) may accept data frames by using an identifier acceptance mask configured in the corresponding local acceptance mask registers (LAMn). The identifier acceptance masks for mailboxes 0 and 1 are configured by 5

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

214

THE CONTROLLER AREA NETWORK MODULE

LAM0_H (high word) and LAM0_L (low word), whereas for mailboxes 2 and 3 are configured by LAM1_H (high word) and LAM1_L (low word). The bitwise layout of LAMn_H is shown in Figure 9-20.

Figure 9-20. Local Acceptance Mask high word6.

The LAMn16-28 bits. Bits 0-12 are the high 13 bits of the local acceptance mask. If LAMn[k] (where k=16, …, 28) is “0”, then the k-th bit of the received identifier, must match the k-th bit in the identifier register (MSGIDn) of the mailbox; if LAMn[k] is “1” (don’t care), then the k-th bit of the received identifier is accepted. The LAMI bit. Bit 15 is the local acceptance mask identifier extension bit. If the LAMI bit is “0”, then the type of identifiers (standard or extended) is defined by the IDE bit in the corresponding MSGIDn register. However, when LAMI is “1”, all identifiers are filtered with the local acceptance mask (standard and extended) regardless of the IDE bit value. 9.3.6 The Local Acceptance Mask Register 0 and 1 Low Word (LAM0_L and LAM1_L) The Local Acceptance Mask Register (LAMn) contains the lower 16 bits of the identifier mask.

Figure 9-21. Local Acceptance Mask Register low word6.

9.3.7 Mailbox Buffers There are four 16-bit word buffers for each of the six mailboxes (MBXnA, MBXnB, MBXnC and MBXnD) containing the eight data bytes (maximum) of a data field received or pending transmission (receive/transmit mailbox). 6

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

215

THE CONTROLLER AREA NETWORK MODULE

Table 9-1 lists the memory locations for message ID high/low word (MSGIDnH/L), message control (MSGCTRL) and mailbox buffers (MBXnA/B/C/D) for all six mailboxes in the CAN module.

Table 9-1. Memory locations for MSGIDn, MBXnA/B/C/D and MSGCTRLn7.

9.3.8 The Mailbox Direction/Enable Register (MDER) The mailbox direction/enable register (MDER) enables mailboxes 0 through 5; additionally, it configures the direction of mailboxes 2 and 3 (receive/transmit).

Figure 9-22. Mailbox Direction/Enable Register (MDER)7.

The ME0-ME5 bits. Bits 0-5 enable/disable the operation of mailboxes 0-5. The MD2 and MD3 bits. Bits 6 and 7 configure the direction of mailboxes 2 and 3. A “0” configures the mailbox as transmit, whereas “1” configures the mailbox as receive.

7

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

216

THE CONTROLLER AREA NETWORK MODULE

9.3.9 The Transmit Control Register (TCR) The transmit control register (TCR) contains flags or control bits related to transmission.

Figure 9-23. Transmit Control Register (TCR)8.

TheTRR2-5 bits. Bits 0-4 are the transmission request reset bits for mailboxes 2, 3 (if configured as transmit), 4 and 5. Writing “1” to the TRR mailbox bit will “lock” the mailbox from data-write actions. Essentially, the TRR bit is a safety precaution that prevents the contents of the transmit mailbox from changing, while the mailbox is scheduled for transmission. If you set this bit to “1” and afterwards request a transmission (with the TRS bit), the mailbox will lock until the transmission is concluded, either successfully, or with an error (including lost arbitration). Practically, you may set this bit shortly after you requested transmission for a specific mailbox in order to protect its contents from changing during the transmission. Note that write access to the mailbox while the TRR bit is set will cause a write denied interrupt flag (WDIF). The bit is automatically reset (“0”) upon successful/unsuccessful conclusion of the transmission. The TRS2-5 bits. Bits 4-7 initiate transmission on mailboxes 2, 3 (if configured as transmit), 4 and 5 by writing a “1”. The TRS bit is reset upon successful/unsuccessful conclusion of the transmission. Hence, you may use the TRS bit to track the process of a transmission. Note that if mailboxes 2 and 3 are configured for auto-reply, the CAN may set the TRS2 or TRS3 automatically; in any other case, TRS bits are set manually by your program. The AA2-5 bits. Bits 8-11 are abort acknowledge flags for mailboxes 2, 3 (if configured as transmit), 4 and 5. If a transmission from a mailbox fails, the corresponding AA flag will be raised. The AA flag will also cause an error interrupt, if enabled. Note that the flag should be manually cleared with a “1”-write action. 8

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

217

THE CONTROLLER AREA NETWORK MODULE

The TA2-5 bits. Bits 12-15 are transmission acknowledge flags for mailboxes 2, 3 (if configured as transmit), 4 and 5. If a transmission of a mailbox is successful in the sense that the ACK bit of the frame appears dominant on the bus (as opposed to the initial recessive value), the corresponding TA flag is raised. Practically, the TA flag signifies that the frame was received by at least one recipient in the network and will trigger a transmit interrupt, if enabled. The flag should be manually cleared with a “1”-write action. 9.3.10 The Receive Control Register As opposed to TCR, the receive control register (RCR) contains flags and control bits related to frame reception.

Figure 9-24. The CAN Receive Control Register (RCR)9.

The OPC0-OPC3 bits. Bits 0-3 are the overwrite control bits for mailboxes 0, 1, 2, 3 (if 2, 3 are configured for reception). If the OPC bit of a mailbox is set to “1”, the contents of the mailbox are protected from being overwritten by a new message, if the current content of the mailbox has not been retrieved and the relative flag (see RPM0-3 bits) has not been cleared by the program. If the OPC bit is “0”, the content of the mailbox is overwritten by the newly arrived data. The RPM0-3 bits. Bits 4-7 are the received message pending flags for mailboxes 0, 1, 2, 3 (if 2, 3 are configured as receive). The RPM flag of a mailbox is raised by the CAN module in the case of frame reception with a matching ID. The RPM flag will also trigger a receive interrupt, if enabled. Note that you should always clear the flag manually with a “1”-write action. The RML0-3 bits. Bits 8-11 are the received message lost flags for mailboxes 0, 1, 2, 3 (if 2, 3 are configured as receive). If the contents of a mailbox are overwritten by the data field of a newly arrived frame while the RPM flag has not been cleared, the corresponding RML flag

9

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

218

THE CONTROLLER AREA NETWORK MODULE

is raised and will cause a mailbox interrupt if enabled (RMLIM bit in CAN_IFR). The RML bit should be manually cleared with a “1”-write action. The RFP0-3 bits. Bits 12-15 are the remote frame pending flags for mailboxes 0, 1, 2, 3. If a remote request arrives and the ID matches the one of a mailbox, then the corresponding RFP flag is raised. If the mailbox is configured for auto-answering (AAM bit in the MSGIDnH), the CAN will take care of the response and consequently clear the flag; however, if the mailbox is not configured for automatic answers to remote requests, you will have to clear the flag manually by writing a “1”. 9.3.11 The Master Control Register (MCR) The master control register (MCR) is used to control the behavior of the core of the CAN module. The MCR is actually configuring the low-level functions that the CAN is responsible to automatically perform, such as “wake up” actions on bus activity, bus enabling following certain periods of consecutive recessive bits on the bus, etc. Although your application is not really concerned with these operations, however, you will have to configure the behavior of the CAN module during the initialization of the peripheral and this is the reason why you should be aware of certain “technical” details regarding the physical and transfer layer of the CAN protocol. Moreover, the MCR enables/disables self-test mode, in which we may operate and test the peripheral without the physical presence of a CAN bus; in fact, we will be using this option in the example that follows.

Figure 9-25. The CAN Master Control Register (MCR)10.

The MNBR0-1 bits. Bits 0-1 (mailbox number) are used to indicate which of mailboxes 2 or 3 (i.e., “10” or “11”) is going to be accessed for data update. The MNBR bits are used exclusively for mailboxes 2 and 3 and only in the case they are configured to reply to remote request frames (AAM bit set). For example, if mailbox 2 is configured with the auto-answer 10

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

219

THE CONTROLLER AREA NETWORK MODULE

(AAM) bit enabled and you wish to update the data, you should first write a value of 2 (“10”) to the MNBR bits and then set the CDR bit (explained shortly) to “1”. Note that if the mailbox is configured for transmission but without having auto-answering enabled, you do not need to access the MNBR bits in order to update the data. The STM bit. Bit 6 is the self test mode bit. If “1”, the CAN module operates in self-test mode, allowing us to test transmission and reception without the actual presence of a bus. In self-test mode, the module transmits the data frame and raises an acknowledge flag (TAn). We will be using this feature in order to test the CAN interrupt in our example shortly. Unfortunately, we cannot track auto-answering operation for remote requests in this mode. If the STM bit is “0”, the CAN is operating in normal mode. The ABO bit. Bit 7 is the auto bus on bit. As mentioned earlier, a CAN node may switch to a bus-off state if a significant number of transmit errors accumulates. If the ABO bit is “0”, the node will remain in the bus-off (high impedance) state even after 128x11 recessive bits; to return to a bus-on state (error active node), you must write a “0” to the CCR bit (explained shortly). If the ABO bit is set to “1”, the node will become error active after 128x11 recessive bits on the bus and the cycle will begin again automatically. The CDR bit. Bit 8 is the change data request bit for mailboxes 2 and 3. If the mailbox (2 or 3) is configured as transmit or has its auto-answer bit (AAM) enabled, then in order to update the mailbox data, you will have to set the CDR bit to “1” before any writing action takes place. Following data update, the CDR should be set to “0”. Note that if the mailbox is configured for auto-answering to remote requests, you should also specify (using the MNBR bits) which of the two mailboxes (2, 3) is going to be accessed. The WUBA bit. Bit 9 is the wake up on bus activity bit. If WUBA is “0”, the CAN module will remain in power-down mode (if enabled) following the appearance of a dominant bit on the bus; if WUBA is “1”, the CAN module automatically leaves power-down mode after the detection of a dominant bit on the bus. The DBO bit. Bit 10 is the data byte order bit. The DBO bit configures the order of the transmitted data bytes. If DBO is “0”, the transmission order is 3, 2, 1, 0, 7, 6, 5, 4; if DBO is “1”, the order is 0, 1, 2, 3, 4, 5, 6, 7. The PDR bit. Bit 11 is the power-down mode bit. If you wish to disable clocking of the CAN controller, you first have to set the module to power-down mode by writing a “1” to the PDR bit. Generally, if the DSP is going to enter IDLE mode (CPU clocks are stopped), you will have to request a power-down from the CAN module.

220

THE CONTROLLER AREA NETWORK MODULE

The CCR bit. Bit 12 is the change configuration request bit. If CCR is set to “0”, normal operation of the CAN module is requested. However, if you wish to configure the bit configuration registers (BCRn), you must first set this bit to “1” and wait until the CCE flag in GSR (see section 9.3.2) is “1”, indicating that access is granted. Additionally, recall that if the auto bus on (ABO) bit is “0”, you will have to set CCR to “0” in order for the module to return to a bus-on state (error active). The SUSP bit. Bit 13 is the suspension bit, configuring the behavior of the peripheral upon emulation suspension. If “0”, the module will stop following completion of any pending transmission; if “1”, the CAN module runs freely. 9.3.12 The Bit Configuration Register 2 (BCR2) The bit configuration register 2 (BCR2) configures the time quantum of the CAN network. Note that, in order to configure BCR2, you must set the change configuration request (CCR) bit in MCR to “1” and wait until the CCE flag in GSR is raised, thereby granting you access to the bit registers (BCR2 and BCR1). The low 8 bits (baud register prescale-BRP bits) of the register are used to configure the time quanta duration, whereas the 8 high bits are reserved. The time quantum of the CAN network is configured in terms of the 8 BRP bits according to:

Apparently, the greatest value for TQ, given a CLKOUT signal at 40 MHz, should be (255+1)/40,000,000 = 6.4 μs, whereas the lowest should be (0+1)/40,000,000=25 ns. The baud rate of the network is calculated as follows (in bits per second):

9.3.13 The Bit Configuration Register 1 (BCR1) The bit configuration register 1 (BCR1) configures the bit period time segments mentioned earlier in this chapter. Again, note that in order to configure BCR2, you must set the CCR bit in MCR to “1” and wait until access is granted (CCE flag in GSR).

221

THE CONTROLLER AREA NETWORK MODULE

Figure 9-26. Bit Configuration Register (BCR1)11.

The TSEG2 bits. Bits 0-2 define the length of PHASE_SEG2 in TQ. Note that TSEG2 should be constrained by the following:

where SJW is the synchronization jump width (explained shortly) and can have a value of 1-4 TQ. The TSEG bits can receive values ranging from 0-7, corresponding to PHASE_SEG2 lengths of 1-8 (TSEG2BITS + 1 = PHASE_SEG2). The TSEG1 bits. Bits 3-6 define the length of PHASE_SEG+PROP_SEG in TQ, according to the following constraint:

The TSEG1 bits can receive values from 0 to 15, corresponding to PHASE_SEG1+PROP_SEG lengths ranging from 1 to 16 (PHASE_SEG1 + PROP_SEG = TSEG1BITS + 1). The SAM bit. Bit 7 configures the sample point. The CAN module samples a bit at the end of PHASE_SEG1 and beginning of PHASE_SEG2. If the SAM bit is “0”, the sampling occurs only once, whereas if “1”, the module samples 3 times and decides with respect to majority. The SJW bit. Bit 8-9 configures the synchronization jump width. As mentioned earlier, the CAN module may decide upon re-synchronizing itself with the bit stream if a transition in the state of the bus falls outside the expected boundaries of the synchronization segment (SYNC_SEG). Re-synchronization occurs by either lengthening the PHASE_SEG1 (at the expense of PHASE_SEG2) interval up to SJW TQ, or lengthening the PHASE_SEG2 (at the expense of PHASE_SEG1) interval by a length equal to the jump width. In any case, the value of SJW defines the magnitude of the correction in TQ. The SJW bits receives values from 0 to 3, 11

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

222

THE CONTROLLER AREA NETWORK MODULE

corresponding to actual values of the jump width ranging from 1 to 4 (JW = SJWBITS+1). Figure 9-27 illustrates the bit period subdivisions with respect to TSEG1, TSEG2 and SJW (sample point “slides” either forward or backward depending on the error phase).

Figure 9-27. Bit period subdivisions with respect to the values of TSEG1, TSEG2 and SJW 12.

The SBG bit. Bit 10 configures synchronization on both edges. By setting this bit to “0”, re-synchronization occurs only on falling edges (recessive to dominant). The CAN specification states that re-synchronization may optionally occur upon rising edges (dominant to recessive); however, “1” is reserved for the SBG bit. As a recommendation, always set this bit to “0”. 9.3.14 The Error Status Register (ESR) The error status register (ESR) contains error flags, mainly related to the operation of the CAN core module, concerning bit transmission, checksum errors, bus operation, etc. Note that the first three flags are automatically cleared by the CAN module, whereas the rest may be cleared by writing a “1”.

Figure 9-28. The Error Status Register12.

12

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

223

THE CONTROLLER AREA NETWORK MODULE

The EW bit. Bit 0 is the warning status flag. If one or both of the receive and transmit error counters exceeds 96 errors, this flag is raised. Recall that if any of these counters exceeds 127, the node will switch state from error active to error passive. The EP bit. Bit 1 is the error passive flag. If the node switches to error passive state, the EP bit becomes “1”, whereas in any other case, it remains “0”. The BO bit. Bit 2 is the bus-off flag. If the node switches to a bus-off state, the BO bit becomes “1”, whereas in any other case, it remains “0”. Recall that, if the auto-bus on (ABO) bit in MCR is set to “1”, then the CAN module will automatically reset to error active state following 128x11 recessive bits; if ABO is “0”, in order to exit bus-off state we need to write a “0” in the CCR bit in MCR. The ACKE bit. Bit 3 is the acknowledge error flag. Actually, this is an indirect error flag. The ACKE bit will become “1”, if the previous transmission did not detect acknowledgement on the bus; the bit is “0” in case of an acknowledgement. The SER bit. Bit 4 is the stuff error flag. This flag is raised (“1”) if no stuffing bit is detected following five consecutive bits of the same polarity on the bus; if “0”, no stuffing error occurred. The CRCE bit. Bit 5 is the CRC error flag. The CRCE bit becomes “1” upon detection of a CRC error. The SA1 bit. Bit 6 is the stuck at dominant flag. The SA1 bit is “1” upon initialization and will change to “0” upon detection of a recessive bit on the bus. Practically, if this flag remains “1” after quite some time following initialization (or reset), then there has been no recessive bit on the bus, which can be interpreted as an erroneous situation. ` The BEF bit. Bit 7 is the bit error flag. The BEF bit reflects the successful transmission of a bit. The flag can be interpreted in two ways: If the CAN module is placing an arbitration field on the bus, then the BEF flag will be raised only if a dominant bit is placed and the result on the bus is recessive. In any other case, if the module is transmitting a bit, then the BEF flag will be raised if the state of the bus is different from the bit placed, regardless of whether it is dominant or recessive. The FER bit. Bit 8 is the form error flag. If “0”, the CAN module was able to transmit and receive correctly; if “1”, then one or more of the frame fields (excluding data) does not appear correctly on the bus.

224

THE CONTROLLER AREA NETWORK MODULE

9.3.15 The Global Status Register (GSR) The global status register (GSR) contains flags related to the overall operation of the CAN module. All flags in GSR are automatically cleared by the CAN module.

Figure 9-29. The Global Status Register13.

The TM bit. Bit 0 indicates whether the CAN module is currently transmitting a message (“1”) or not (“0”). The RM bit. Bit 1 indicates whether the CAN module is currently transmitting a message (“1”) or not (“0”). The PDA bit. Bit 3 is the power down acknowledge flag. If the PDA bit becomes “1”, then the CAN module is in power-down mode. Recall that in order to switch-off the CAN module clock, you will have to request a power-down from the module using the PDR bit in MCR; you will then have to wait until the PDA flag becomes “1” before shutting down the CAN module clock. The CCE bit. Bit 4 is the change configuration flag. If the CCE bit becomes “1”, then the CAN module has granted configuration access to BCR1 and BCR2. Recall that in order to gain access to BCR1 and BCR2, you will have to set the change configuration request (CCR) bit in MCR. The CCE flag indicates that access has been granted; if CCE is “0”, then access to BCR1 and 2 is denied. The SMA bit. Bit 5 is the suspend mode acknowledge flag. If “1”, then the CAN module is in suspend mode. This flag relates to the SUSP bit in MCR.

13

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

225

THE CONTROLLER AREA NETWORK MODULE

9.3.16 The CAN Error Counter Register (CEC) The CAN error counter register (CEC) is a read-only register containing the count of transmission and reception errors. As mentioned earlier, the CAN module uses these counters in order to switch states. The CEC high byte contains the transmission error count (TEC), whereas the low byte contains the reception error count (REC). 9.3.17 The CAN Interrupt Flag Register (CAN_IFR) The CAN module essentially triggers two distinct types of interrupts: The CAN mailbox interrupt and the CAN error interrupt. However, these interrupts maybe requested upon a wide variety of events, such as transmit, receive, change of state, etc. The CAN interrupt flag register (CAN_IFR) contains the flags corresponding to all possible events that may lead to a CAN mailbox or error interrupt. Interrupt flags should be cleared manually by writing a “1” to the appropriate bit in CAN_IFR; however, in case of a mailbox interrupt, the corresponding transmit (TAn) or receive (RMPn) flag in TCR or RCR should be cleared as well.

Figure 9-30. The CAN Interrupt Flag Register14.

The WLIF bit. Bit 0 is the warning level interrupt flag. The flag is raised if any of the error counters has reached the warning level (96). The interrupt request is cleared by writing a “1” to WLIF only (the associated warning status bit, EW is read-only). The EPIF bit. Bit 1 is the error passive interrupt flag. The flag is raised and triggers an interrupt, once the module has switched to error passive state. The request is reset by clearing only the EPIF bit with a “1”-write action.

14

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

226

THE CONTROLLER AREA NETWORK MODULE

The BOIF bit. Bit 2 is the bus-off interrupt flag. The flag is raised and causes an interrupt once the CAN module has switched to bus-off state. The request is reset by writing a “1” to the BOIF bit only. The WUIF bit. Bit 3 is the wake up interrupt flag. The flag is raised and triggers an interrupt upon leaving sleep mode. The request is reset by writing a “1” to the WUIF bit only. The WDIF bit. Bit 4 is the write denied interrupt flag. The flag is raised and triggers an interrupt if a write access to a mailbox has been denied. The request is reset by writing a “1” to the WDIF bit only. The AAIF bit. Bit 5 is the abort acknowledge interrupt flag. The flag is raised and triggers an interrupt, when a transmission has been aborted. The AAIF flag is associated with the AA2-4 (AAn) flags in TCR. To clear the request, the AAIF bit and the corresponding AAn bit in TCR should be cleared by writing a “1”. The RMLIF bit. Bit 6 is the receive message lost interrupt flag. The flag is raised and triggers an interrupt if the data of mailbox are lost (overwritten by new data without being previously read). The request is reset by clearing the RMLIF bit with a “1”-write action. The flag is associated with the RML0-3 flags in RCR. You should also clear the corresponding RML bit by writing a “1”. The MIF0-5 (MIFx) bits. Bits 8-12 are the mailbox interrupt flags for mailboxes 0-5. Each of these flags will trigger a mailbox interrupt, either receive or transmit, with respect to the direction of the mailbox. The MIFn flag should be manually cleared with a “1”-write action. Additionally, if the mailbox is transmit, the corresponding transmit acknowledge (TAn) flag in TCR should also be cleared by writing a “1”; if the mailbox is configured as receive, then the corresponding RPMn (RPM0-3) flag in RCR should also be cleared by writing a “1”. 9.3.18 The CAN Interrupt Mask Register (CAN_IMR) Then CAN interrupt mask register (CAN_IMR) enables/disables the interrupts listed in CAN_IFR. There is a one-to-one correspondence between the bits of the two registers, with the addition of two interrupt priority bits (MIL and EIL). A “1” enables the interrupt and a “0” disables it.

227

THE CONTROLLER AREA NETWORK MODULE

Figure 9-31. The CAN Interrupt Mask Register.

The additional bits contained in CAN_IMR are the following: The EIL bit. Bit 7 configures the error Interrupt priority level. If the EIL bit is set to “0”, the CAN error interrupt is configured for high priority requests; if “1” the CAN error interrupt is configured for low priority requests. The MIL bit. Bit 15 configure the mailbox interrupt priority level. If the MIL bit is set to “0”, the CAN mailbox interrupt is configured for high priority requests; if “1”, the CAN mailbox interrupt is configured for low priority requests. 9.3.19 Initialization of the CAN module Following the conclusion of a rather extensive “tour” through the CAN module registers, it is time to add an initialization routine to our project. As mentioned earlier, we will be using mailbox 0 for data reception and mailbox 2 for transmission in self-test mode. Create a new file named “CANFunc.c” and type the initialization routine that follows: #include "DSP24_Can.h" #include "DSP24_Core.h" void initCAN(void) { // 1. Configuring Master Control Register (MCR) MCRbits->bit.STM = 1; // Self-test mode enabled MCRbits->bit.ABO = 1; // Auto-bus ON MCRbits->bit.CDR = 0; // no write action (mboxes 2 and 3) MCRbits->bit.WUBA = 0; // wake up on bus activity disabled MCRbits->bit.DBO = 1; // Data Byte Order = 0, 1, ..., 7 MCRbits->bit.PDR = 0; // no action MCRbits->bit.SUSP = 0;

// Stop on suspend (after completing transmission)

228

THE CONTROLLER AREA NETWORK MODULE // Requesting configuration change for BCR1 and BCR2 MCRbits->bit.CCR = 1; // wait until access is granted while (GSRbits->bit.CCE == 0); // 2. Configuring the Time Quantum (TQ) and bit period // BCR2 BCR2bits->bit.BAUDPSC = 255; // maximum tq (255/40,000,000=6.375 usec) // BCR1 BCR1bits->bit.TSEG2 BCR1bits->bit.TSEG1 BCR1bits->bit.SAM = BCR1bits->bit.SJW = BCR1bits->bit.SBG =

= 6; = 10; 1; 3; 0;

// // // // //

7 tq for PHASE_SEG2 (must be SJW+1 - 8) 11 tq for PHASE_SEQ2 (must be>=TSEG2 and 3-16) Sample 3 times on sample point 4 tq bit synchronization jump width synchronization on falling edge

// Clearing configuration access to BCR1 and BCR2 MCRbits->bit.CCR = 0; // wait until access restricted while (GSRbits->bit.CCE == 1); // 3. Disabling mailboxes 0 and 2 for ID configuration MDERbits->bit.ME0 = 0; // disable mailbox0 MDERbits->bit.ME2 = 0; // diable mailbox2 // 4. Setting up Local Acceptance Masks // Local Acceptance mask 0 (mboxes 0 and 1) LAM0_Hbits->bit.LAM = 0; LAM0_Hbits->bit.LAMI = 0; // Acceptable ID type is defied by IDE *LAM0_L = 0;

// Will be using exact ID match

// Local Acceptance mask 1 (mboxes 2 and 3) LAM1_Hbits->bit.LAM = 0; LAM1_Hbits->bit.LAMI = 0; // Acceptable ID type is defied by IDE) *LAM1_L = 0;

// Will be using exact ID match

// 5. Configuring mailbox identifiers // Configuring ID for Mailbox 2 (transmit) MSGID2Hbits->bit.IDE = 0; //Standard 11-bit ID MSGID2Hbits->bit.AME = 0; // IDs must be EXACT match MSGID2Hbits->bit.AAM = 0; // Automatic answer disabled MSGID0Hbits->bit.IDH = 0x14; // Bits 2-12 store matching ID = 5 *MSGID2L = 0;

// using standard ID

// configuring ID for Mbox 0 (receive) MSGID0Hbits->bit.IDE = 0; MSGID0Hbits->bit.AME = 0; MSGID0Hbits->bit.AAM = 0;

229

THE CONTROLLER AREA NETWORK MODULE MSGID2Hbits->bit.IDH = 0x14; // Bits 2-12 store matching ID = 5 *MSGID0L = 0;

// using standard ID

// 6. Configuring mailbox overwrite protection RCRbits->bit.OPC0 = 0;// mailbox0 overwrite protection disabled // 7. Configuring Interrupts // clearing appropriate mailbox flags TCRbits->bit.TA2 = 1; RCRbits->bit.RMP0 = 1; // clearing all interrupt flags (CAN_IFR) CAN_IFRbits->all = 0xffff; // Enabling interrupts (CAN_IMR) CAN_IMRbits->bit.WLIM = 0; // disable warning level interrupt CAN_IMRbits->bit.EPIM = 0; // disable error passive interrupt CAN_IMRbits->bit.BOIM = 0; // disable bus off interrupt CAN_IMRbits->bit.WUIM = 0; // disable bus wake-up interrupt CAN_IMRbits->bit.WDIM = 0; // disable write denied interrupt CAN_IMRbits->bit.AAIM = 0; // disable abort acknowledge interrupt CAN_IMRbits->bit.RMLIM = 0; // disable received message lost interrupt CAN_IMRbits->bit.MIM0 = 0; // disable mailbox 0 interrupt CAN_IMRbits->bit.MIM1 = 0; // disable mailbox 1 interrupt CAN_IMRbits->bit.MIM2 = 1; // enable mailbox 2 interrupt CAN_IMRbits->bit.MIM3 = 0; // disable mailbox 3 interrupt CAN_IMRbits->bit.MIM4 = 0; // disable mailbox 4 interrupt CAN_IMRbits->bit.MIM5 = 0; // disable mailbox 5 interrupt CAN_IMRbits->bit.EIL = 0; // High error interrupt priority (ignored) CAN_IMRbits->bit.MIL = 0; // High Mailbox interrupt priority // 8. Configuring direction and enabling mailboxes (MDER) MDERbits->bit.ME0 = 1; // enable mailbox0 MDERbits->bit.MD2 = 0; // mailbox2 is transmit Mbox MDERbits->bit.ME2 = 1; // enable mailbox2 // 9. Enabling core interrupt INT1 (CAN high priority interrupts) *IMR |= 1; }

Save the file and let us go through this relatively extensive initialization sequence. Although several register fields are not necessary in the configuration, they are accessed for demonstration purposes. It is not necessary to keep this particular order while configuring the CAN registers, however, enabling the mailboxes should come last. As a first step, we configure the master 230

THE CONTROLLER AREA NETWORK MODULE

control register (MCR) by setting the self-test (STM) bit to “1”, since we will be running the CAN module in self-test mode for now. Also, the auto-bus ON (ABO) bit is set to “1”, in order to configure the CAN module to automatically reset from bus-off states to the error active state (following 128x11 recessive bits). The CDR (Change Data Request) bit is set to “0” (no action) since we will not be accessing mailbox(es) 2, 3 for data update at this point. The DBO (data byte order) bit is set to “1” for a 0, 1, 2, 3, 4, 5, 6, 7 order of bytes in the data field. Finally, the power-down mode (PDM bit) is set to “0” (no action) for normal CAN operation. The next step involves configuring bit configuration registers 1 and 2 (BCR1 and BCR2). Recall that in order to configure BCR1 and BCR2 we need to request access by writing a “1” to the configuration change request (CCR) bit in MCR and thereafter, wait until the configuration change enable (CCE) flag in GSR is raised. Hence, the following code should always precede the code accessing BCR1 and BCR2: // Requesting configuration change for BCR1 and BCR2 MCRbits->bit.CCR = 1; // wait until access is granted while (GSRbits->bit.CCE == 0);

The lower 8 bits of BCR2 configure the time quantum (TQ) of the network. The TQ length is set to its greatest possible value (BAUDPSC = 255) which should be 6.4 μs, given that the CPU clock runs at 40 MHz. In BCR1, the length of TSEG1 (PROP_SEG+PHASE_SEG1) is set to 11 (i.e., TSEG1BITS = 10) and the value of TSEG2 (PHASE_SEG2) is set to 7 (i.e., TSEG2BITS = 6). The synchronization jump width (SJW) is 4 (i.e., SJWBITS = 3). Note that the TSEG2 length should be constrained by the relationship, , which, in our case, is true (i.e., ). The SAM (sample point) bit is set to “1” for three sampling actions. The bit period (bit time) can now be calculated as:

We may now calculate the baud rate of the network as follows:

Before moving on to the configuration of other registers, the access status of BCR1 and BCR2 should be restored (restricted). Therefore, the CCR bit in MCR should be set to “0” this time and thereafter, wait until the CCE flag is cleared (“0”). Hence, the following code should always come after the configuration of BCR1 and BCR2: 231

THE CONTROLLER AREA NETWORK MODULE // Clearing configuration access to BCR1 and BCR2 MCRbits->bit.CCR = 0; // wait until access is restricted while (GSRbits->bit.CCE == 1);

The next step involves the configuration of mailbox 0 and 2 for reception and transmission respectively. The mailboxes should be disabled prior to configuring the relative identifier and masks. Therefore, the first action should be to set the appropriate bits in the mailbox direction/enable register (MDER). MDERbits->bit.ME0 = 0; // disable mailbox0 MDERbits->bit.ME2 = 0; // disable mailbox2

Although the message identifier registers 0 and 2 (MSGID0 and MSGID2) will be configured to ignore the local acceptance masks, the code includes the setup of LAM0 and LAM1 for demonstration purposes. The LAMI bit is set to “0” to allow the IDE bit in MSGID to specify the type of acceptable identifiers (standard/extended). All mask bits in LAM0_H, LAM1_H and LAM0_L, LAM1_L are set to “0” for an exact bit match of the incoming frame IDs with the IDs of mailboxes 0 and 2. Recall that LAM0 configures the acceptance mask for mailboxes 0 and 1, while LAM1 configures the acceptance mask for mailboxes 2 and 3; therefore, we need to configure both in this case. In MSGID0H and MSGID2H, the AAM bit is set to “0” to disable auto-answering, the IDE is also “0” for standard IDs (11 bits) only and the AME (acceptance mask enable) bit is set to “0” to ignore the local acceptance mask (although even if we choose to enable the mask, the results will be the same, since all acceptance mask bits are “0” for exact match). Both ID fields are set to 5 (00000000101b) in MSGID0H and MSGID2H IDH fields. Recall that when using standard CAN ID (11 bits), the identifier must be stored in bits 2-12 of the IDH field of the relative MSGIDnH register (i.e., MSGID0H and MSGID2H); therefore, the value assigned to IDH is 14h, which corresponds to the binary equivalent of 5 in bits 2-12 of IDH (Figure 9-32).

Figure 9-32. Standard ID stored in bits 2-12 of IDH (MSGIDnH).

232

THE CONTROLLER AREA NETWORK MODULE

Using equal IDs for two mailboxes configured as receive and transmit respectively is not a very wise selection when configuring the CAN module for a real application. The module will attempt to “trace” a frame and look for an acknowledgement in the ACK field on the bus following a transmission; adding a receive mailbox with the same ID may confuse the module and even more likely, cause it to ignore the receive mailbox completely, since it is solely devoted to transmission. Generally, mailboxes should have unique IDs within a single station. Since we are not using a real CAN bus, selecting the same ID for receive and transmit mailboxes will not cause any issues. Note that the CAN module scans the mailboxes for an ID match starting from mailbox 5 moving down to 0. If at any point, the CAN matches the incoming ID with that of a mailbox, it will terminate the scan and handle the message with respect to the mailbox that first matched the incoming ID. The last configuration step should involve the CAN interrupt registers. Since some of the interrupt flags are associated with status flags in TCR and RCR, it is a wise move to clear the flags that correspond to the mailboxes enabled in the application. In our case, the receive message pending for mailbox 0 (RMP0) in RCR and the transmit acknowledge bit for mailbox 2 (TA2) are cleared by writing a “1”. All interrupt flags in CAN_IFR are cleared by writing FFFFh to the entire register. The only interrupt enabled in CAN_IMR is the mailbox 2 interrupt, which should correspond to an acknowledge flag following transmission of a frame. Note that in selftest mode, the CAN module generates its own acknowledge flag, thereby triggering the corresponding mailbox interrupt, if enabled. Lastly, notice the interrupt priority bits for mailbox and error interrupts (MIL and EIL) are set to “0” for high priority requests served by core interrupt INT1 (Figure 9-33). Obviously, since there are two separate bits for mailbox (MIL) and error (EIL) interrupt priority, it is not necessary to configure both at the same level.

Figure 9-33. Core interrupt-peripheral interrupt vector for high priority Mailbox/Error interrupts15.

15

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

233

THE CONTROLLER AREA NETWORK MODULE

To initiate operation of the CAN module, mailboxes 0 and 2 are enabled (ME0, ME1 = 1) with mailbox 2 configured for transmission (MD2 = 0) in the mailbox direction/enable register (MDER). The last line of initCAN() enables core interrupt INT1 corresponding to CAN high priority interrupt requests. 9.3.20 A Routine for Transmission We now need to add one more function to “CANFunc.c” to initiate transmission of a data frame from mailbox 2. Place the following code after the end of function initCAN(): void sendCANMsg(unsigned int *msg) { // waiting until until mailbox is not transmitting while (TCRbits->bit.TRR2 != 0); // writing data to the mailbox MCRbits->bit.CDR = 1; *MBX2A *MBX2B *MBX2C *MBX2D

= = = =

1; 0; 0; 0;

MCRbits->bit.CDR = 0; // Configuring frame as Data MSGCTRL2bits->bit.RTR = 0; // RTR = 0, // Data Length is 1 byte MSGCTRL2bits->bit.DLC = 1; // Now requesting transmission while (TCRbits->bit.TRS2 != 0); TCRbits->bit.TRS2 = 1; }

Function sendCANMsg() receives an array of 16-bit long positive integers as parameter, copies it verbatim to mailbox 2 and initiates a frame transmission. For precaution only, a while statements delays execution until the TRR2 flag is clear. Recall that we may use the TRRn bits to lock the transmit mailbox until a transmission is completed. If, by any chance, the mailbox has been locked from write access at the beginning of a previous transmission, we don’t want to attempt to write to that mailbox until the transmission has been completed (or aborted). The next step is to access the contents of mailbox 2 (MBX2A-D). I am sure you do not need to be reminded that the change data field request (CDR) bit in MCR must be set to “1” before mailboxes 2, 3 are accessed for data update. Following updates, the CDR bit should be set back to “0”. 234

THE CONTROLLER AREA NETWORK MODULE

The preparation of the data frame concludes with the configuration of the relative control field (MSGCTRL2). The remote request (RTR) bit is set to “0” to configure the frame as “data” and the data length code (DLC) bits are set to “1” for one byte of data. The last step is to set the TRS2 (transmission request set for mailbox2) bit to “1” and let the CAN module place our frame on the bus. 9.3.21 I/O Configuration The CAN receive and transmit pins (CANRX and CANTX) are multiplexed with general I/O pins IOPC6 and IOPC7 in port C (Figure 9-34) and therefore must be configured in I/O mux control register B (MCRB) to their primary function. Open file “DSP24_Sys.c” and replace function initIO() with the code listed below: void initIO(void) { MCRBbits->bit.CANTX_GPIOC6 = 1; // I/O pin C6 is CAN transmit MCRBbits->bit.CANRX_GPIOC7 = 1; // I/O pin C7 is CAN receive }

Figure 9-34. Locations of the CANRX and CANTX pin on the DSP I/O header16.

9.3.22 CAN Interrupt Service Routines The CAN module has numerous interrupt sources, all of which trigger either a CAN mailbox or error interrupt. Since the CAN module is configured for high priority interrupt requests, we will need to add two new case branch statements to the CAN high priority mailbox and error ISRs. Open file “DSP24_DefaultISR.c” and make the highlighted modifications in the INT1 GISR routine: interrupt void INT1_GISR(void){ unsigned int peripheral;

16

eZdsp™ LF2407A– Technical Reference (Spectrum Digital)

235

THE CONTROLLER AREA NETWORK MODULE

asm(" SETC INTM"); peripheral = *PIVR; asm(" CLRC INTM");

Case branches to the CAN high priority Mailbox and Error interrupts

switch (peripheral) { case 0x0004: // ADC Mailbox High Priority interrupt ADCINTHP_ISR(); break; case 0x0040: // CAN mailbox High Priority interrupt CANMBINTHP_ISR(); break; case 0x0041: // CAN Error High Priority Interrupt CANERINTHP_ISR(); break;

} // reset the core INT1 flag by writing a "1" in bit 0 of IFR *IFR|=1; }

We will now have to add the two specific interrupt service routines. Add the two following functions, preferably after the SPI ISR: interrupt void CANMBINTHP_ISR(void) { unsigned int MsgPending0, TransmitAcknowledge2;

CAN Mailbox High Priority Interrupt Service Routine

TransmitAcknowledge2 = TCRbits->bit.TA2; MsgPending0 = RCRbits->bit.RMP0; // clearing acknowledge flag in TCR TCRbits->bit.TA2 = 1; // clearing interrupt flag CAN_IFRbits->bit.MIF2 = 1; asm (" NOP"); } interrupt void CANERINTHP_ISR(void) { asm(" NOP"); }

CAN Error High Priority Interrupt Service Routine

Function CANMBINTHP_ISR() is the CAN mailbox high priority specific interrupt service routine. The acknowledge flag TA2 is copied to variable TransmitAcknowledge2 and the message pending flag RMP0 is copied to variable MsgPending0 for debugging purposes. The only interrupt source enabled is mailbox 2, therefore the ISR should be triggered only by the TA2 flag. Regardless, we should be able to monitor a possible message reception by mailbox 0 simply by polling the relative flag (RMP0) in RCR. Function CANERINTHP_ISR() is the CAN error SISR. Recall that all interrupts are disabled except the one for mailbox 2, therefore the execution should not reach this routine 236

THE CONTROLLER AREA NETWORK MODULE

at any time during execution. However, it will be useful for a bit of experimentation with the CAN configuration in later sections. Don’t forget to make the appropriate updates in “DSP24_DefaultISR.h” and add the header file “DSP24_Can.h” in the include section. 9.3.23 The main() function The main() function of the project is quite similar to the ones used in previous examples, including system, I/O and CAN initialization. Function sendCANMsg is repeatedly invoked inside the main loop. Create a file named “CANmain.c” and type the following code: extern extern extern extern

void initSystem(void); void initCAN(void); sendCANMsg(unsigned int *msg); initIO(void);

void main() { unsigned int msg[] = { 0, 1, 2}; initSystem(); initIO(); initCAN(); // enable interrupts asm(" CLRC INTM"); // main loop while (1) sendCANMsg(msg); }

The project browser window now should look like the one in Figure 9-35.

Figure 9-35. Project window for “CAN.pjt”.

237

THE CONTROLLER AREA NETWORK MODULE

9.3.24 Building and Executing If all went well, you should be able to build the project and download the program to the DSP. Before executing, place a breakpoint inside the mailbox ISR as shown in Figure 9-36.

Figure 9-36. Breakpoint inside the CAN mailbox ISR.

Open a watch window, add variables TransmitAcknowledge2 and MsgPending0 and run the program. Normally, program execution should stop on the breakpoint and the watch window should look like the one in Figure 9-37.

Figure 9-37. Watch window upon execution suspend.

The CAN module generates an acknowledgement in self-test mode, which triggers the mailbox interrupt. However, mailbox 0 never receives a message and therefore the flag (RMP0) remains always “0”.

238

THE CONTROLLER AREA NETWORK MODULE

9.3.25 Tracking the data frame on the transmission line Although we do not have a CAN bus with at least one more station, we still can verify some of the CAN protocol principles by looping the CANTX line back to the CANRX line of the DSP. This way, we may “deceive” the CAN module into “believing” that it is connected to a real CAN bus and have the opportunity to observe the arbitration field being transmitted on the CANTX pin with an oscilloscope. First, we need to make a few slight modifications in our code. In the initialization function, locate the self-test mode bit assignment and make the necessary updates in order for the module to operate normally. MCRbits->bit.STM = 1; // Self-test mode enabled MCRbits->bit.STM = 0; // Self-test mode disabled

Previous Statement New Statement

In the same function, enable the bus-off error interrupt in the CAN_IMR bitwise setup. CAN_IMRbits->bit.BOIM = 0; // disable bus off interrupt

Previous Statement

CAN_IMRbits->bit.BOIM = 1; // enable bus off interrupt

New Statement

In the main() function, remove the sendCANMsg() call inside the main loop and place it right before the beginning of the loop. // main loop while (1) sendCANMsg(msg); sendCANMsg(msg); // main loop while (1);

Previous Statements

New Statements

Save the changes, rebuild and download the program. Remove the breakpoint from the CAN mailbox ISR and add a new one inside the CAN error ISR as shown in Figure 9-38.

Figure 9-38. Breakpoint inside the CAN error ISR.

239

THE CONTROLLER AREA NETWORK MODULE

Before running, use a jumper wire to short the CANRX, CANTX (pins 27 and 28 in Figure 9-34) pins and connect your oscilloscope to one of the ends. Use pin 39 or 40 for ground. If you now run the program, the oscilloscope should be displaying a repetitive pattern such as the one shown in Figure 9-39.

Figure 9-39. Repetitive waveform on the CANTX-CANRX pins.

The CAN module is transmitting the waveform shown between the dashed lines on the oscilloscope display. This waveform is actually our data frame (excluding the ACK, EOF and 2 bits of the CRC field). The CAN module is transmitting over and over again until it gets an acknowledgment and since there is no one at the other end (except for itself), the transmission is not intercepted and therefore acknowledged by any node; the module holds back for a while and then it starts all over again. Recall that we moved the call to sendCANMsg() outside of the main loop, therefore transmission is requested only once. However, the CAN module is attempting to send the frame again following the intermission and idle state (notice the extended period of recessive bits to the right of the display). If we now take a closer look at the waveform, we will recognize the start of frame, arbitration field, control field and one data byte as shown in Figure 9-40. Recall that TQ is 6.4 μs and the bit period is 19 TQ. Therefore, the bit time should be approximately 121 μs. If you are already trying to “read” the waveform, keep in mind that you should remove the stuffed bits.

240

THE CONTROLLER AREA NETWORK MODULE

Figure 9-40. Data frame fields shown on the waveform.

The oscilloscope timing cursors 1 and 2 are placed on the two far remote edges of the waveform; however, the rightmost edge is NOT the end of the frame, since it also contains a series of 9 recessive bits (ACK and EOF) which cannot be accurately measured due to the lack of edges. If you now take away the stuffed bits and start noting the bitstream on your notebook, you will eventually see the binary equivalents for the ID (00000000101b), SOF and RTR (0b) and Data (00000001b). Moreover, you can actually use the stuffing bits to your advantage in this task, since you can be sure that there is a trail of five bits of the same polarity, preceding that particular stuffed bit. Now, If your DSP is running, try to carefully disconnect one end of the jumper wire shorting the CANRX and CANTX pins. After a small amount of time, you should be able to see an execution stop inside the CAN error ISR as shown in Figure 9-41. Recall that this time we enabled the bus-off error interrupt. After the loop-back connection is broken, the CANRX line receives an extensive series of dominant bits (“0”) and therefore the module switches to a bus-off state. It is a good idea to check the CAN flags affected by the previous action with the aid of local variables inside the ISR. Moreover, if you included the declaration of the can error counter (CEC) in the relative header file, you may also observe the transmit and receive error count. 241

THE CONTROLLER AREA NETWORK MODULE

Figure 9-41. Execution suspension inside the CAN error ISR after removing the loop-back connection.

9.4 Summary/Conclusions In my humble experience, communications is a tough issue whether between humans or machines. When trying to set the rules of a conversation, one always finds that there are unforeseen circumstances in which the implemented protocols may not provide the desired consensus. When it comes to machines, a communication breakdown may stall an entire system (whereas with humans, it will most probably mean war); therefore, designing and implementing a network should involve a maximum number of safety precautions. The CAN protocol is based on relatively simple principles and the CAN module incorporates the functions that implement these principles. However, given the numerous possible conditions of the bus, a safe network should consult and utilize a maximum number of status flags to avoid deadlocks. It is strongly recommended that you go through the relative chapter in the TMS320LF/C240xA DSP Controllers Reference Guide- System and Peripherals and create more complete programs in terms of the possible bus conditions that may present themselves in a real network. To do this, you should use as many status flags as possible and provide the corresponding routines for the various combinations of values. Since CAN modules are rather popular with several types of commercial microcontrollers or DSPs, setting up a real CAN bus shouldn’t be a hard task given the availability of resources.

242

THE CONTROLLER AREA NETWORK MODULE

243

THE DISCRETE FOURIER TRANSFORM

10

The Discrete Fourier Transform

10.1 Overview on Fourier Analysis The destiny of a DSP predominantly involves the manipulation of signals. Essentially, a signal can be any real function of time. However, engineers always wish to expand this perspective by looking at signals from different angles as well. The overall theory behind this approach is extensive, with plenty of equations involving both real and complex numbers. On the other hand, the actual applications implement several tedious or less-tedious, in terms of development, algorithms based on these equations. One may think of the math as the “mountain-top view” on signals and the corresponding algorithms as a rather narrow, yet very clear and specific picture. In order to implement algorithms, you should first consult the math, while the algorithms can give you a great insight into what the math tells you. 10.1.1 Complex Numbers as a Representation of Phase and Magnitude Complex numbers are prevalent in signal theory. Although you do not have to be well acquainted with their properties, understanding the essence of their symbolic representation is quite significant. From the point of view of mathematicians, the set of complex numbers is an expansion of the set of real numbers to include a special “number”, sometimes called the imaginary radical, such as that, when raised to the power of 2, the result is -1 (i.e., j2 = -1). A complex number can also be seen as a vector of certain length (absolute value) and angle (argument) from the real axis as shown in Figure 10-1. However, from the point of view of engineers, a complex number is a representation of a cosine wave in terms of its phase and magnitude. Specifically, the absolute value of the complex number corresponds to the magnitude and the argument, to the phase. Take for example, function f(t) = 5cos(2πt+π/4) with ω=2π and magnitude 5. The corresponding complex number Zf should then be:

Sometimes, the phasor notation is used instead of the polar form:

244

THE DISCRETE FOURIER TRANSFORM

Euler’s identity is the most popular representation used, since it provides the means for a more convenient notation, while computations can be facilitated by the properties of the exponential:

Generally,

Figure 10-1. A cosine wave and the corresponding complex number on the complex plane.

Regardless of notation, complex numbers hold properties essential to the computations required in dealing with signals in terms of their phase and magnitude. A certain level of familiarity with their attributes would help gaining a better insight into the contents of the following sections. 10.1.2 Fourier Analysis Fourier analysis is a set of techniques aiming at decomposing a signal into cosine waves. Jean Baptist Joseph Fourier (1768-1830) presented a paper in 1807, based on which, every periodic function can be written as the summation of cosines of specifically chosen magnitudes and frequencies. In fact, to extend Fourier’s claim, every signal can be approximated by a series of cosines, if the decomposition is performed on bounded time intervals. There are very specific exceptions to this rule, but they exist only in the minds of mathematicians and therefore, do not concern engineers. The fundamental aspects of Fourier analysis involve basically two distinct notions regarding the form that a signal may assume: The time domain signal and the frequency 245

THE DISCRETE FOURIER TRANSFORM

domain signal. The time domain form contains the values of the signal through time, whereas the frequency domain form contains the values of phase and magnitude of the sinusoidal waves to which the signal is decomposed. Each form is equivalent to the other and describes the signal completely (or approximates it to a certain extent). To clarify this with an example, consider a periodic signal in the time domain defined by the following:

Notice that f is already conveniently written as a sum of cosines. Also notice that the angular frequency of the second term is double the angular frequency of the first (ω1=1, ω2=2). In fact, the fundamental concept of Fourier analysis involves approximating (or converging towards) the function with a finite sum or series of cosines with angular frequencies being integer multiples of a minimum frequency (called the fundamental frequency), denoted as ω0. From this point of view, if we choose ω0 = 1, we have f(t) exactly in the form that we want it. The frequency domain signal is a new function derived from the time domain signal, mapping the various frequencies involved in the sum (i.e., ω0=1, ω1=2) to the respective magnitudes and phases, in other words, the complex numbers that correspond to each of the cosines in the sum. If we set aside complicated math for a moment and act purely on intuition, we may observe the following relations between the cosines involved in the equation of f and the complex numbers that represent their magnitude and phase:

Based on these observations, we may now define the frequency domain function as follows:

Although we violated several fundamental concepts of Fourier analysis and you probably feel that you need to make amends to the gods of mathematics, you will be relieved to know that function F, is indeed, a frequency domain function of f. Unfortunately, whether approximating the signal with a finite or infinite sum of cosines, the process of finding the frequency domain signal is not that simple at all. However, you may keep conclusions: The frequency domain is a function that maps the frequency components (cosines) of the sum to their respective magnitudes and phases.

246

THE DISCRETE FOURIER TRANSFORM

10.1.3 The Fourier Series Assume that you wish to approximate a periodic function f(t) with an infinite or finite series of cosines. Also, assume that you wish to use ω0 as fundamental frequency and therefore, the cosine components will have frequencies ω1=ω0, ω2=2ω0, ..., ωκ=κω0, .. . Then, if is the approximation of f, it can be calculated by the following series:

The infinite sum that approximates f(t) is called the Fourier series, while coefficient ak is actually the value of the frequency domain function for frequency kω0. Notice that the Fourier series runs from to . Also, in case you are wondering how is it possible having a sum of complex exponentials producing a real number as a result, notice that also ak is a complex number; therefore, throughout computations, the imaginary components are eliminated. Depending on the selection of the fundamental frequency ω0, the sum may approximate f(t) well enough, even within a few terms. In other cases, it may not converge at all even with infinite terms. Generally, if f is bounded in a bounded time interval (considered zero for every other time instance out of this interval), the series will converge. The multiples of the fundamental frequency 2ω0, 3ω0, ..., κω0, ..., are called harmonics. If we now go back to the function of section 10.1.2, and choose ω0 = 1, then the coefficients of the Fourier series for the first two harmonics can be calculated as follows (intermediate calculations are omitted):

247

THE DISCRETE FOURIER TRANSFORM

Interestingly, the results are looking much like the ones we deduced by observation in the previous section. Note that computations require that we calculate the frequency domain value for both kω0 and - kω0. The “Negative” frequency –kω0 corresponds to the same harmonic component (cosine) with kω0. In fact, all frequency components for are described by two conjugate Fourier coefficients. Although the math concurs with our intuition, calculations are not over yet! We still have to calculate the frequency domain value for k = 0 (a0). Coefficient a0 corresponds to the constant component of the signal, sometimes referred to as DC offset. In our example, a0 is found to be 0:

This is obvious, since there is no constant term involved in the equation of f(t). However, if f(t), involved a constant term, say, equal to 3, such as:

then, the corresponding frequency domain value for 0ω0 = 0 would be 3:

In short, the frequency domain comprises an odd number of coefficients, a-k, a-(k-1), …, a-1, a0, a1, …, ak-1, ak corresponding to the harmonic components (cosines) of the signal, with frequencies 0, ω0, ..., (k-1)ω0 , kω0; each harmonic component produces a pair of conjugate values (a-k, ak) in the frequency domain, except for the 0-frequency (DC offset) component which produces only one value (a0). The sum may also be infinite, although in some cases, such as in our example, the coefficients will become 0 after a certain value of k. Greater values of k will generally produce higher degree of convergence of the sum. Depending on the chosen function, the series may not converge at all, but this is an issue that shouldn’t keep us worried throughout this text. The frequency domain function F(kω0) = ακ maps frequencies to complex numbers, therefore, we need two distinct graphs to draw it: One for magnitude and one for phase. To go back to our example, the frequency domain function F(kω) for f(t) =cost + 2cos(2t+π/3) should be the following:

248

THE DISCRETE FOURIER TRANSFORM

Although we decided to use only two harmonic components in the sum, if you put yourself in the trouble to make a few calculations, you will realize that all coefficients for are 0:

This means that the series converged with only five terms and our frequency domain is 100% equivalent to the original time domain signal with only two harmonic components!

Figure 10-2. Magnitude and phase of the signal in the frequency domain.

The process of decomposing the time domain signal into the frequency domain values is called analysis, as opposed to the reverse process, called synthesis. The frequency domain, also called frequency spectrum, can reveal certain attributes that may be of critical importance in the further processing of the signal. In fact, obtaining the frequency spectrum of a signal, in a great number of occasions, is much more useful to engineers than the actual time domain form. 10.1.4 The Time-Continuous Fourier Transform Although the Fourier series applies to periodic functions, the concept can be expanded to apply to any function under certain conditions. Specifically, if we assume that the fundamental frequency ω0 is converging to 0, then the period T = 2π / ω0 is also converging to infinity. In this case, the frequency domain coefficients become a continuous function of frequency and can be calculated by,

249

THE DISCRETE FOURIER TRANSFORM

Function F is known as the continuous-time Fourier transform and can provide the continuous frequency domain of any signal, given that the integral converges. The inverse transform can be found in a similar manner:

In practice, the continuous-time Fourier transform does not correspond to an algorithm that can be implemented inside a computer program. However, you may think of it as a mathematical tool which can give us a peek at the fully continuous frequency domain of a function and enable us to cross-check our results when calculating the Fourier coefficients. 10.1.5 The Fourier series for Discrete-Time Signals (Discrete Fourier Transform) Signals are continuous functions of time and potentially infinitely long, but we can only store a finite number of discrete-time sampled values in the memory of the DSP at a time. Under these conditions, the techniques for the computation of the Fourier coefficients do not apply exactly as stated in the previous section; however, the overall principle remains the same. If we wish to obtain the frequency domain of a discrete-time signal of finite length, then the discrete Fourier transform (DFT) is the mathematical technique that should be employed. The DFT computes the time domain of a signal using a finite number of sampled values for the analysis. Specifically, let N be an even number of samples x*0+, …, x*N-1], then the time domain signal X=[a0 a1 … aN-1] can be calculated as follows:

The coefficient can now be found with the use of a sum instead of an integral and therefore, it can be easily calculated inside a program loop. The fundamental frequency is always chosen to be ω0 = 2π/Ν and the number of samples (N) is called fundamental period. What lies under the mat here, is the fact that we silently assume that the N samples are a period of an infinitely long periodic signal. Of course, this is not true in most cases; however, for that particular “window” of N values, the DFT will produce the frequency domain nicely! The standard notation for the Fourier coefficients (frequency domain signal) is X[k] (instead of the ak, used so far), while the time domain signal is denoted with x[k]. Also, ReX[k] and ImX[k] are the real and imaginary parts of X[k]. To synthesize the time domain signal, the process is almost identical: 250

THE DISCRETE FOURIER TRANSFORM

10.1.5 The DFT algorithm The DFT algorithm uses N time domain values, where N is usually chosen to be a power of 2 (but can also be any even number), to compute the corresponding N frequency domain values. The DFT algorithm is relatively easy to implement and involves the following sums for the analysis (forward DFT):

For synthesis, also known as the inverse DFT, the process is quite similar. However, be mindful that the sums are not divided by N.

Figure 10-3 illustrates the three collections of data, x, ReX and ImX involved in the process of forward and inverse DFT.

Figure 10-3. The three collections of data involved in the forward and inverse DFT.

251

THE DISCRETE FOURIER TRANSFORM

The DFT algorithm discussed in this section is simple to implement and produces reliable results. However, it is not used in most DSP applications, since it is significantly slow compared to its fast counterpart, the fast Fourier transform (FFT). The FFT produces the same results with great performances, compared to the very slow, simple DFT. Unlike the simple DFT, the FFT is complicated enough to confuse even the most experienced programmers, but this a price that developers are willing to pay in exchange for a highly optimized application. We will be initially implementing the simple DFT algorithm as two nested summation loops in order to monitor results in the oscilloscope in the example that follows. The program will be enhanced with an FFT routine in later sections.

10.2 A Program to perform signal Analysis using the simple DFT Following a few equations and possibly, several new concepts, I trust you are ready to return back to the thrill of typing some more code! In this program, we will be generating a periodic pulse using the compare pin of timer 1 and feeding it to ADC input channel 1 (ADCIN1). The ADC will be taking a number of samples and the program will be performing a forward DFT on the sampled values. To make things even more interesting, the magnitudes of the frequency domain values will then be sent, via the SPI output (SPISIMO), to a digital to analog converter (DAC). This way, we will be able to monitor the magnitudes of the frequency spectrum with our oscilloscope. If it all sounds too much, let us start with the classics: First create a new folder and project named “DFT”. Add files “DSP24_Ev.h”, “DSP24_Ev.c”, “DSP24_Adc.h”, “DSP24_Adc.c”, “DSP24_Core.h”, “DSP24_Core.c”, “DSP24_Spi.h”, “DSP24_Spi.c”, “DSP24_Sys.c”, “DSP24_DefaultISR.h”, “DSP24_DefaultISR.c”, “DSP24_Sys.c”, “2407_cmd.cmd” and “cvectors.asm”. Finally, add library file “rts2xx.lib”. 10.2.1 Modifying the Linker Command File Throughout all the examples and until now, we used the very same linker command file. If you take a look inside the SECTIONS clause, sections stack and sysmem are allocated inside internal SARAM and B1 memory: .stack: > .sysmem: >

DSARAM B1

PAGE 1 PAGE 1

/* uninitialized */ /* uninitialized */

This configuration worked nicely in the previous examples, since we were not making much use of the stack and our program data was limited to a few variables. In this example, we will obviously need some extra memory space to store at least three arrays of double precision floating point numbers and possibly, several other temporary variables that may have to be at 252

THE DISCRETE FOURIER TRANSFORM

least a few tenths of memory words long; therefore, the sysmem section must be greater than 10K words. Moreover, it is likely that functions will use numerous local variables to accommodate calculations, which will burden the stack. To provide the necessary memory space, the stack and sysmem sections are now being relocated to external memory as shown in the SECTIONS clause of the linker command file. Unfortunately, we are “trading” ultra-fast on-chip memory with much slower, but larger external RAM in favor of some extra data space. SECTIONS { /* Sections generated by the C compiler .text: > EXTPROG PAGE 0 .cinit: > EXTPROG PAGE 0 .const: > B1 PAGE 1 .switch: > EXTPROG PAGE 0 .bss: > EXTDATA PAGE 1 .stack: > EXTDATA PAGE 1 .sysmem: > EXTDATA PAGE 1 /* Sections declared by the user */ vectors: > VECS PAGE 0 }

*/ /* /* /* /* /*

initialized */ initialized */ initialized */ initialized */ uninitialized */

/* uninitialized */ /* uninitialized */ /* initialized */

If you made the changes, save the file and go to Project Build Options and open the Linker tab as shown in Figure 10-4. Set heap size to 25000 and stack size to 5000. These values seemed adequate enough for me, but you can use greater/lower figures, as long as they serve program needs and don’t exceed the size of the DSP’s external data RAM.

Figure 10-4. Configuring stack and heap size in the Build Options window.

253

THE DISCRETE FOURIER TRANSFORM

10.2.2 Initialization of the ADC The ADC is the most important peripheral in our application. We will be using it to sample the time domain signal and store the sampled values upon the relative interrupt request. It will be operating with a cascaded sequencer for 16 samples per conversion session on ADC input channel 1 (ADCIN1). Although we want the ADC to run indefinitely, it is configured for start-stop operation mode and the reset occurs at the end of the interrupt service routine (see relative section). This way, we prevent the cascaded sequencer from automatically resetting before all results have been read. Acquisition window is set for 32xTCLK with the ADC clock set to a frequency equal to the frequency of the CPU clock (approximately, 17.2 μs for 16 auto-conversions). The ADC starts with a software trigger and the interrupt requests are low priority. All peripheral (ADC, Timer 1, SPI) initialization routines can be placed in the same file; therefore, it would be wise to have a header file with the relative declarations. Create a file named “Peripherals.h” and type the following: #ifndef PERIPHERALS_H #define PERIPHERALS_H #include #include #include #include #include

"DSP24_Core.h" "DSP24_Ev.h" "DSP24_ADC.h" "DSP24_Spi.h" "Globals.h"

void initADC(void); void initTimer1(void); void initSPI(void); void sendWordSPI(unsigned int wrd); #endif

Save the changes, and create the corresponding file, “Peripherals.c”. Now, type the initialization routine of the ADC: #include "Peripherals.h" void initADC(void) { // 1. Configuring ADCTRL1 ADCTRL1bits->bit.STESTENA = 0; ADCTRL1bits->bit.HILO = 0; ADCTRL1bits->bit.BRGENA = 0; ADCTRL1bits->bit.CALENA = 0; ADCTRL1bits->bit.SEQCASC = 1;

// // // // //

self-test mode disabled no effect in normal mode no effect in normal mode calibration mode disabled cascaded sequencer mode

254

THE DISCRETE FOURIER TRANSFORM ADCTRL1bits->bit.INTPRI = 1; ADCTRL1bits->bit.CONTRUN = 0; ADCTRL1bits->bit.CPS = 0; ADCTRL1bits->bit.ACQPS = 15; ADCTRL1bits->bit.SOFT = 0; ADCTRL1bits->bit.FREE = 0; ADCTRL1bits->bit.RESET = 0;

// low priority ADC interrupt // start-stop mode // Conversion ClOCK = CLKOUT/1 // Acquisition window = 32*Tclk=32*Clkout // 16 x sampling time = 17.2us // immediate stop on suspend // no action for now

// 2. Configuring ADCTRL2 ADCTRL2bits->bit.EVBSOCSEQ2 = 0; // EVB does not start sequencer2 ADCTRL2bits->bit.INTFLAGSEQ2 = 1; // clear SEQ2 interrupt flag ADCTRL2bits->bit.INTENASEQ2 = 0; // disable SEQ2 interrupt ADCTRL2bits->bit.SOCSEQ2 = 0; // no action ADCTRL2bits->bit.RSTSEQ2 = 0; // no action ADCTRL2bits->bit.EXTSOCSEQ1 = 0; // no action ADCTRL2bits->bit.EVASOCSEQ1 = 0; // EVA cannot trigger SOC on SEQ1 ADCTRL2bits->bit.INTFLAGSEQ1 = 1; // clear interrupt SEQ1 flag ADCTRL2bits->bit.INTENASEQ1 = 1; // enable SEQ1 interrupt (cascaded) ADCTRL2bits->bit.SOCSEQ1 = 0; // no action ADCTRL2bits->bit.RSTSEQ1STRTCAL = 0; // no action ADCTRL2bits->bit.EVBSOCSEQ = 0; // EVB cannot start cascaded sequencer // 3. MAXimum CONversion number (MAXCONv) *MAXCONV = 15; // 16 conversions by the cascaded sequencer // 4. Input Channel Select Sequencing // Sampling Channel 1 (16 times - oversampling) CHSELSEQ1bits->all = 0x1111; CHSELSEQ2bits->all = 0x1111; CHSELSEQ3bits->all = 0x1111; CHSELSEQ4bits->all = 0x1111; // 5. enable core INT6 interrupt *IMR |= 0x0020; // 6. Start Cascaded Sequencer ADCTRL2bits->bit.SOCSEQ1 = 1; }

10.2.3 Initialization of Timer 1 for Pulse Width Modulation Since we don’t have a signal source, we can generate our own signal as a periodic pulse, using timer 1. The timer is configured to operate in continuous up/down count mode, with a clock frequency equal to the one of the CPU clock. The period register (T1PR) is set to 750 (approximately 18.75 μs period length), and the compare register (T1CMP) is set to 500. These settings should produce a square pulse, 12.5 μs long, with a period of 37.5 μs. Expand “Peripherals.c” with the following code for timer 1 initialization: void initTimer1(void) { GPTCONAbits->bit.T1PIN=02; // T1CMP Compare output active high GPTCONAbits->bit.T2PIN=00; // T2CMP Comp output active low

255

THE DISCRETE FOURIER TRANSFORM GPTCONAbits->bit.TCOMPOE=1; // Enable EVA GP Timer Compare outputs GPTCONAbits->bit.T1TOADC=00; // Do not start ADC with Timer1 GPTCONAbits->bit.T2TOADC=00; // Do not start ADC with Timer2 // Timer 1 Initialization *T1CNT = 0; *T1PR = 750; // 750/40x10^6 = 18.75 usec timer period *T1CMPR = 500; // 25 usec signal period (pulse length = 12.5 usec ) T1CONbits->all = 0; T1CONbits->bit.TCMPREN = 1; // Enable Timer Compare T1CONbits->bit.TCLD = 00; // reload compare reg on underflow T1CONbits->bit.TMODE = 01; // Continuous Up/Down mode T1CONbits->bit.TPS = 000; // Input Clock PreScale /x1 T1CONbits->bit.TCLKS = 00; // Internal Clock source select T1CONbits->bit.SOFT = 0; T1CONbits->bit.FREE = 0; // immediate stop on emulation suspend // disable EVA interrupts EVAIMRAbits->all = 0; EVAIMRBbits->all = 0; T1CONbits->bit.TENABLE = 1; // Enable Timer 1 }

The rectangular pulse generated on T1CMP should look like the one shown in Figure 10-5.

Figure 10-5. Periodic pulse generated on the timer 1 compare pin.

It is important to know what kind of magnitude curve you should be expecting to see in the frequency domain for the pulse of Figure 10-5. The continuous-time Fourier transform can be of great help at this point. Let us isolate one period of the wave, in a way such as that the rising edge of the pulse occurs at -6.25 μs, and the falling edge at +6.25 μs, as shown in Figure 10-6. The main reason 256

THE DISCRETE FOURIER TRANSFORM

we are displacing the pulse, is to avoid unnecessary calculations in the Fourier integral. Specifically, by selecting these particular time instances for the pulse edges, we eliminate the imaginary elements from the time domain and facilitate our observations. However, arbitrary selection of time instances for the pulse edges will eventually produce equivalent graphs in the frequency domain.

Figure 10-6. A single period of the rectangular pulse produced on T1CMP.

We can now compute the continuous-time Fourier transform for our pulse; the pulse height is assumed to be 1, x(t) is the time domain signal and X(ω), the Fourier transform:

Function Χ(ω) is a version of a popular function amongst engineers, known as the sinc function, (sinc(x) = sinx/x), and since it contains only real parts, we may obtain its plot in the frequency domain as illustrated in Figure 10-7.

Figure 10-7. The continuous-time Fourier transform (sinc function) of the rectangular pulse.

257

THE DISCRETE FOURIER TRANSFORM

The plot of Figure 10-7 has many secrets to reveal for our simple pulse. Perhaps, the most important for now, is that the frequency domain obtained with the discrete Fourier transform will introduce similar patterns. Since we will be dumping magnitudes, the expected spectacle on our oscilloscope must be something like the plot shown on of Figure 10-8b.

Figure 10-8a. Magnitude plot of the continuous-time Fourier transform, X(ω).

Figure 10-8b. Magnitude plot of the sampled version of X(ω), X[k] = X(2πk/Ν) .

Although we will not be sampling the continuous-time Fourier transform (bur rather the opposite), the result, taking the necessary scaling into consideration, should be very much similar to the plot of Figure 10-8b. To be absolutely fair, the values calculated by the DFT are likely to slightly deviate from the actual values of the transform, since we are summing discrete samples instead of integrating a continuous line and the overall magnitude is distributed into a finite number of frequencies (as opposed to the infinite frequencies of X(ω)). Moreover, recall that the discrete Fourier transform runs for k = 0..N, which will cause a displacement of the graph of Figure 10-8b to the positive side of the frequency axis. Nevertheless, the result is bound to have a pattern resembling a sampled version (in a samplehold fashion) of the magnitude of the continuous-time Fourier transform, X(ω). 258

THE DISCRETE FOURIER TRANSFORM

10.2.4 Initialization of the SPI The LF2407A does not come with an on-chip digital to analog converter, but you can always buy a commercial integrated DAC circuit for a very small price these days. In this example, we will be using the TLC5615™ 10-bit digital to analog converter IC by Texas Instruments shown in Figure 10-9.

Figure 10-9. The TLC5615 10-bit Digital to Analog Converter 8-pin package1.

The DAC receives a 10-bit integer via an SPI input (DIN) and produces the relative voltage on the output pin (OUT). Nominal operation voltage, VDD, is +5V and, although there is a +5V pin on the DSP board, you should NOT use it to power the DAC. The REFIN is connected to 2.5V (nominal is 2.048V); a simple voltage divider is used to get the 2.5 V on the pin, but you can use different resistor values or any other apparatus in order to get closer to 2.048 V. Pins , SCLK and DIN are the SPI chip-select, clock and data input, connected to the , SPICLK and SPISIMO DSP lines. Figure 10-10 illustrates connections.

Figure 10-10. Connections of the TLC5615 10-bit DAC with the LF2407A. 1

TLC5615C, TLC5615I 10-bit Digital-to-Analog Converters (Texas Instruments)

259

THE DISCRETE FOURIER TRANSFORM

The SPI module is configured to operate as master, transmitting 16-bit long characters. The TLC5615 requires clocking with a maximum frequency of 14 MHz. The baud register (BRR) is set to 0, which corresponds to approximately a quarter of the CPU clock frequency (CLKOUT/4 = 10 MHz), providing the fastest possible baud rate and it should be acceptable by the DAC. Add the following SPI initialization routine to “Peripherals.c”: void initSPI(void) { // 1. Configuring SPI Configuration Control register (SPICCR) SPICCRbits->bit.SPIRST = 0; // Reset SPI flags. Output inactive SPICCRbits->bit.CHARLEN = 15; // 16-bit character length SPICCRbits->bit.CLKPOL = 0; // data is sent on rising edge // 2. Configuring Operation Control Register (SPICTL) SPICTLbits->bit.OVRNINTENA = 0; // Disable receiver overrun interrupt SPICTLbits->bit.CLKPHSEL = 1; // Normal SPI Clock Phase SPICTLbits->bit.MST_SLV = 1; // Device is Master SPICTLbits->bit.TALK = 1; // Enable transmission SPICTLbits->bit.INTENA = 0; // Disable interrupts // 3. Baud rate register (SPIBRR) configuration *SPIBRR = 0; // maximum bps rate (CLKOUT/4=10,000,000) for 40Mhz CLKOUT // 4. Configuring SPI Priority Control Register (SPIPRI) SPIPRIbits->bit.SPIPRI = 1; // Low priority interrupt requests SPIPRIbits->bit.SPISOFT = 0; SPIPRIbits->bit.SPIFREE = 0; // immediate stop on suspend //5. SPI ready for transmission/reception SPICCRbits->bit.SPIRST = 1; }

To conclude file “Peripherals.c”, we need to include a routine for transmission: void sendWordSPI(unsigned int wrd) { // wait until transmit buffer is available while (SPISTSbits->bit.TxBUFFULL!=0); // place the 16-bit word *SPITXBUF = wrd; }

Before moving on, it should be noted that the DAC requires the 10-bit digital value to be stored in bits 2-11 (Figure 10-11) of the 16-bit SPI word. This means that we should be shifting the data by 2 positions to the left before sending it with sendSPIWord().

260

THE DISCRETE FOURIER TRANSFORM

Figure 10-11. The 16-bit SPI input data layout for the TLC5615 DAC.

10.2.5 Global Data and DFT Routines It would be wise to have a separate header file with all global data involved in the DFT, as well as flags and indexes related to sampling, decomposition and data transmission. Create a header file named “Globals.h” and add the following declarations: #ifndef GLOBALS_H #define GLOBALS_H #include #define TRUE 1 #define FALSE 0 #define PI 3.14159265 #define N 256 #define mDFT 0 #define mFFT 1 // time domain signal x[] extern double x[]; // frequecy domain signals extern double ReX[]; // real part extern double ImX[]; // imaginary part // magnitudes extern double Magnitudes[]; // ADC index for the Time Domain signal extern unsigned int TDindex; // Index of amplitude value to be transmitted to DAC extern unsigned int OutIndex; // DFT enable flag extern int EnableDFT; // variable initialization void initVars(void); // Computes ReX, ImX with the simple DFT void computeDFT(void);

261

THE DISCRETE FOURIER TRANSFORM // transmit N magnitudes to the DAC void dumpSignal(void); #endif

Save the changes and create the respective C file, “Globals.c”: #include "Globals.h" // time domain signal double x[N]; // frequency domain signal double ReX[N]; // real part double ImX[N]; // imaginary part // Magnitudes double Magnitudes[N]; // ADC index for the Time Domain signal unsigned int TDindex; // DFT enable flag int EnableDFT; // variable initialization (and allocation) void initVars(void) { EnableDFT = FALSE; TDindex = 0;

Initialization of global variables

} void computeDFT(void) { unsigned int n, k; double R, I;

The simple DFT

for (k = 0; k < N ; k++) { R = I = 0; for (n = 0; n < N; n++) { R += x[n] * cos(2 * PI * n *k / (1.0*N)); I += - x[n] * sin(2 * PI * n *k / (1.0*N)); } // normalizing sums by 1/N R /= (1.0*N); I /= (1.0*N); // storing magnitude Magnitudes[k] = sqrt(R*R + I*I); // storing ReX and ImX ReX[k] = R; ImX[k] = I; } }

262

THE DISCRETE FOURIER TRANSFORM

// send N magnitudes to the DAC void dumpSignal(void) { unsigned int i, wrd; for (i=0; ibit.SOCSEQ1 = 1; }

Notice that the ADC is being reset and restarted at the end of the ISR. This is equivalent to continuous run operation, but we chose not to use it. The reason is because, if the ADC resets automatically and the ISR is still reading results, the DSP will perceive this as an access violation and will probably cause a non-maskable interrupt. To avoid such complications, the ADC is configured for start-stop mode operation and the reset and restart of the autoconversion sequence occur safely after all results have been read. The ADC is configured for low priority interrupt requests. This means that you will have to update “DSP24_DeafultISR.h” with the relative declaration. Moreover, since low priority ADC interrupts correspond to core level INT6 interrupts, we will have to add code to the relative general interrupt service routine in “DSP24_DefaultISR.c”: interrupt void INT6_GISR(void) { unsigned int peripheral; asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM"); switch (peripheral) { case 0x0004: // ADC low priority ADCINTLP_ISR(); break; default: break; } // reset INT6 flag by writing a "1" in bit 5 of IFR *IFR|=0x20; }

264

THE DISCRETE FOURIER TRANSFORM

10.2.7 I/O initialization The SPISIMO, SPICLK, and T1CMP pins should be configured for primary operation. Replace the old initIO() function in “DSP24_Sys.c” with the following (SPISOMI is also configured): void initIO(void) { // Timer 1 MCRAbits->bit.T1PWMT1CMP_GPIOB4 = 1; // SPI // setting up SPI pins MCRBbits->bit.SPISIMO_GPIOC2 = 1; MCRBbits->bit.SPISOMI_GPIOC3 = 1; MCRBbits->bit.SPISTE_GPIOC5 = 1; MCRBbits->bit.SPICLK_GPIOC4 = 1;

// // // //

// T1 Compare pin enabled

Use Use Use Use

the the the the

pin pin pin pin

as as as as

SPISIMO SPISOMI SPISTE SPICLK

}

10.2.8 The main() function The ADC ISR will set EnableDFT to TRUE following the N-th sample. The DFT computations will be invoked in the program main loop only when EnableDFT is TRUE. Following the transmission of all magnitudes to the DAC, EnableDFT is set back to FALSE in order to start a new sampling session. #include "Globals.h" #include "DSP24_Sys.h" #include "Peripherals.h" void main() { unsigned int METHOD = mDFT, i; initSystem(); initVars(); initTimer1(); initADC(); initSPI(); initIO(); // enable global interrupt switch asm(" CLRC INTM"); while (1) { if (EnableDFT == TRUE) { if (METHOD == mDFT) // Computing simple DFT computeDFT(); else { Computing FFT

265

THE DISCRETE FOURIER TRANSFORM // transposing time domain /* transposeTDSignal1(); computeFFT1(); */ } // dump the signal 10,000 times for (i=0; ibit.ACQPS = 15; ADCTRL1bits->bit.SOFT = 0; ADCTRL1bits->bit.FREE = 0; ADCTRL1bits->bit.RESET = 0;

// // // // // // //

self-test mode disabled no effect in normal mode no effect in normal mode calibration mode disabled cascaded sequencer mode low priority ADC interrupt start-stop mode

// ADC ClOCK = CLKOUT/2 // Acquisition window = 32*Tclk=32*Clkout // (16 x samp.time = 34.4 usec conv. time) // immediate stop on suspend // no action for now

// 2. Configuring ADCTRL2 ADCTRL2bits->bit.EVBSOCSEQ2 = 0; // EVB does not start sequencer2 ADCTRL2bits->bit.INTFLAGSEQ2 = 1; // clear SEQ2 interrupt flag ADCTRL2bits->bit.INTENASEQ2 = 0; // disable SEQ2 interrupt ADCTRL2bits->bit.SOCSEQ2 = 0; // no action ADCTRL2bits->bit.RSTSEQ2 = 0; // no action ADCTRL2bits->bit.EXTSOCSEQ1 = 0; // no action ADCTRL2bits->bit.EVASOCSEQ1 = 0; // EVA cannot trigger SOC on SEQ1 ADCTRL2bits->bit.INTFLAGSEQ1 = 1; // clear interrupt SEQ1 flag ADCTRL2bits->bit.INTENASEQ1 = 1; // enable SEQ1 interrupt (cascaded) ADCTRL2bits->bit.SOCSEQ1 = 0; // no action (clear pending SOC triggers) ADCTRL2bits->bit.RSTSEQ1STRTCAL = 0; // no action ADCTRL2bits->bit.EVBSOCSEQ = 0; // EVB cannot start cascaded sequencer // 3. MAXimum CONversion number (MAXCONv) *MAXCONV = 3; // 16 conversions by the cascaded sequencer // 4. Input Channel Select Sequencing CHSELSEQ1bits->all = 0x1111; // channel #1 sampling CHSELSEQ2bits->all = 0x1111; // channel #1 sampling CHSELSEQ3bits->all = 0x1111; // channel #1 sampling CHSELSEQ4bits->all = 0x1111; // channel #1 sampling // 16 samples from channel #1 (oversampling) // 5. enable core INT6 interrupt *IMR |= 0x0020;

309

DIGITAL FILTERS // Start Cascaded Sequencer ADCTRL2bits->bit.SOCSEQ1 = 1; } void initTimer1(void) { GPTCONAbits->bit.T1PIN=0x2; // T1CMP Compare output active high GPTCONAbits->bit.T2PIN=00; // T2CMP Comp output active low (not used) GPTCONAbits->bit.TCOMPOE=1; // Enable all GP Timer Compare outputs GPTCONAbits->bit.T1TOADC=00; // Do not start ADC with Timer1 GPTCONAbits->bit.T2TOADC=00; // do not start ADC with Timer2 // Timer 1 Initialization *T1CNT = 0; *T1PR = 48000; // 1.2 ms timer period *T1CMPR = 2000; // 2.3 ms pulse length T1CONbits->all = 0; T1CONbits->bit.TCMPREN = 1; // Enable Timer Compare T1CONbits->bit.TCLD = 00; // reload compare reg on underflow T1CONbits->bit.TMODE = 01; // Continuous Up/Down mode T1CONbits->bit.TPS = 0; // Input Clock PreScale /x1 T1CONbits->bit.TCLKS = 0; // Internal Clock source select T1CONbits->bit.SOFT = 0; T1CONbits->bit.FREE = 0; // immediate stop on emulation suspend // enable Timer 1 Period interrupt EVAIFRAbits->bit.T1PINT = 1; // clear T1PINT flag EVAIMRAbits->bit.T1PINT = 1; // enable interrupt // enable core interrupt INT 2 (timer 1) *IMR |= 2; // start the timer T1CONbits->bit.TENABLE = 1; // Enable Timer 1 } void initSPI(void) { // 1. Configuring SPI Configuration Control register (SPICCR) SPICCRbits->bit.SPIRST = 0; // Reset SPI flags. Output inactive SPICCRbits->bit.CHARLEN = 15; // 16-bit character length SPICCRbits->bit.CLKPOL = 0; // data is sent on rising edge // 2. Configuring Operation Control Register (SPICTL) SPICTLbits->bit.OVRNINTENA = 0; // disable receiver overrun SPICTLbits->bit.CLKPHSEL = 1; // Normal SPI Clock Phase SPICTLbits->bit.MST_SLV = 1; // Device is Master SPICTLbits->bit.TALK = 1; // Enable transmission SPICTLbits->bit.INTENA = 0; // Disable interrupts // 3. Baud rate register (SPIBRR) configuration *SPIBRR = 0; // maximum bps rate (CLKOUT/4=10,000,000) for 40Mhz CLKOUT // 4. Configuring SPI Priority Control Register (SPIPRI)

310

DIGITAL FILTERS SPIPRIbits->bit.SPIPRI = 1; // Low priority interrupt requests SPIPRIbits->bit.SPISOFT = 0; SPIPRIbits->bit.SPIFREE = 0; // immediate stop on suspend //5. enabling the spi SPICCRbits->bit.SPIRST = 1; } void sendWordSPI(unsigned int wrd) { // wait until transmit buffer is available while (SPISTSbits->bit.TxBUFFULL!=0); // place the 16-bit word *SPITXBUF = wrd; }

The ADC is configured for 16 samples in cascaded operation with the clock running at half the frequency of the CPU clock. This is done mainly to “lengthen” the time between consecutive ADC interrupts in order for the convolution computations to take place during that period. Depending on the number of kernel points, the summation routine may be significantly time consuming. Although the ADC retrieves 16 samples in a conversion run, the program will only use the result stored in RESULT0 as the new input (the rest of the sampling steps in the auto-conversion are simply used as a delay). There are, however, ways to optimize the time of the convolution computations; one of them is to write the corresponding routine in assembly as a callable assembly function and make use of the multiply and accumulate assembly commands of the LF2407A (but this implies that coefficients and inputs are stored as integers instead of floating point numbers). Additional optimizations in the C code may include the use of pointers to array locations (instead of using indexes), which will remove a few extra background calculations from the loop. Regardless of optimization techniques, a good solution is the one that does the job and no matter how simple it may look, you should not try to make things more elaborate than they already are. The timer 1 compare output is used to create long pulses, approximately 2.3 ms long, with periodic “disturbances” of very narrow pulses which may seem as “spikes” in the overall signal. You can think of the periodic pulse as a nice step input for our filters. Also, notice that the period interrupt is enabled; we will be using it to periodically adjust the content of the compare register in order to change the length of the pulse. Header file “Peripherals.h” remains unchanged from the example of the previous chapter. In case it is not available, or you wish to review it without having to run back several pages, you may type or inspect the following code: #ifndef PERIPHERALS_H #define PERIPHERALS_H

311

DIGITAL FILTERS

#include #include #include #include #include

"DSP24_Core.h" "DSP24_Ev.h" "DSP24_ADC.h" "DSP24_Spi.h" "Globals.h"

void initADC(void); void initTimer1(void); void initSPI(void); void sendWordSPI(unsigned int wrd); #endif

11.2.6 Input shifting and Convolution Calculation of the output is an implementation of the method described in section 11.1.4. The input buffer shifts to the right, while the products of past M inputs are summed together, including the product of the newest input with the first coefficient in the respective array. Following calculations, the function scales the output as an integer between 0 and 1023 and transmits it to the DAC via the SPI. The routine is fairly simple and can be subjected to optimizations, if necessary. Add the following code to “Globals.c”: void shiftConvolve(double input) { int i; unsigned int wrd; double sum; sum = 0; // convolving and shifting input buffer for (i=M; i>0; i--) { // shifting x[i] = x[i-1]; // summing sum += kernel[i] * x[i]; } // inserting newest input in the first position x[0] = input; // adding the last term sum += kernel[0]*x[0]; // transmitting result to DAC wrd = (unsigned int)(sum * 1023.0); // shift two bits right wrd = wrd B1 PAGE 1 /* initialized */ .switch: > EXTPROG PAGE 0 /* initialized */ .bss: > EXTDATA PAGE 1 /* uninitialized */ .stack: > DSARAM PAGE 1 /* uninitialized */ .sysmem:

>

B1

PAGE 1

/* Sections declared by the user */ vectors: > VECS PAGE 0 }

/* uninitialized */ /* initialized */

Also, remove any references to interrupt service routines from “cvectors.asm”, leaving only the branch instruction to c_int0 upon reset. Branch to the c_int0 on reset is kept

.ref _c_int0 .sect "vectors"

Branches to ISRs removed rset:

B

_c_int0

;00h reset

int1: int2: int3: int4: int5: int6:

B B B B B B

int1 int2 int3 int4 int5 int6

;02h ;04h ;06h ;08h ;0Ah ;0Ch

int7: int8: int9: int10: int11:

B B B B B

int7 int8 int9 int10 int11

;0Eh ;10h ;12h ;14h ;16h

INT1 INT2 INT3 INT4 INT5 INT6

reserved INT8 (software) INT9 (software) INT10 (software) INT11 (software)

352

TOPICS OF SPECIAL INTEREST int12: int13: int14: int15: int16: int17: int18: int19: int20: int21: int22: int23: int24: int25: int26: int27: int28: int29: int30: int31:

B B B B B B B B B B B B B B B B B B B B

int12 int13 int14 int15 int16 int17 int18 int19 int20 int21 int22 int23 int24 int25 int26 int27 int28 int29 int30 int31

;18h ;1Ah ;1Ch ;1Eh ;20h ;22h ;24h ;26h ;28h ;2Ah ;2Ch ;2Eh ;30h ;32h ;34h ;36h ;38h ;3Ah ;3Ch ;3Eh

INT12 (software) INT13 (software) INT14 (software) INT15 (software) INT16 (software) TRAP NMI reserved INT20 (software) INT21 (software) INT22 (software) INT23 (software) INT24 (software) INT25 (software) INT26 (software) INT27 (software) INT28 (software) INT29 (software) INT30 (software) INT31 (software)

Create a main function in “afmain.c” and a separate file named “func1.c”. The project view window should look like Figure 12-4.

Figure 12-4. Project browser window for “AsmFunc.pjt”.

353

TOPICS OF SPECIAL INTEREST

Now, open “func1.c” and add the following function: void convolve(int *h, int *x, int N,int input, unsigned int result;

int *acl, int* ach) {

N=9; result = *h + *x; input = 2; *acl = 5; *ach = 7; input = 1; }

Function convolve() assigns a value to parameter N, adds the contents of pointers x and h, performs value assignments to the contents of pointers acl and ach and lastly, assigns a value of 1 to parameter input. Obviously, the code is somehow irregular and has no practical use in terms of results. However, the way that instructions are placed could lead to the generation of readable assembly code, suitable for modifications with respect to our goals. The next step is to add a call to convolve() from main(). Open “afmain.c” and add the following code: extern void convolve(int*, int*, int, int, int*, int*); void main() { int h = 11; int x = 12; int ach, acl; convolve(&h, &x, 2, 4, &acl, &ach); while (1) { } }

We are almost ready! Now, open Project Build Options and highlight Feedback on the Compiler tab. At the Interlisting drop-down box, select “C and ASM (-ss)”, as shown in Figure 12-5. Next, highlight category Assembly and check “Keep generated .asm files” as shown in Figure 12-6.

354

TOPICS OF SPECIAL INTEREST

Figure 12-5. Selecting C and assembly interlisting.

Figure 12-6. Enabling keeping of generated assembly files.

355

TOPICS OF SPECIAL INTEREST

Now, build the project and browse to the relative folder. You should be able to see the .asm files generated by the compiler. Locate “func1.asm” and open it. The generated assembly code for C function convolve() should be the following: ******************************************************* * TMS320C24xx ANSI C Codegen Version 7.04 ******************************************************* ; dspac -q -d_DEBUG -v2xx -iC:/CCStudio_v3.1/c2400/cgtools/include func1.c C:\DOCUME~1\George\LOCALS~1\Temp\func1.if ; dspopt NOT RUN ; dspcg -o -q -v2xx -o C:\DOCUME~1\George\LOCALS~1\Temp\func1.if C:\DOCUME~1\George\LOCALS~1\Temp\func1.asm C:\DOCUME~1\George\LOCALS~1\Temp\func1.tmp .port File name. It should be changed to the name of the .asm file .file "func1.c" (i.e., “convasm.asm”) .text .sym _convolve,_convolve,32,2,0 .globl _convolve .func 3 ****************************************************** * FUNCTION DEF : _convolve ****************************************************** _convolve: LF1

convolve() starts here

.set

0

POPD SAR SAR LARK LAR

*+ AR0,*+ AR1,* AR0,2 AR0,*0+,AR2

.sym .sym .sym .sym .sym .sym .sym .line

_h,-3+LF1,20,9,16 _x,-4+LF1,20,9,16 _N,-5+LF1,4,9,16 _input,-6+LF1,4,9,16 _acl,-7+LF1,20,9,16 _ach,-8+LF1,20,9,16 _result,1,14,1,16 1 void convolve(int *h, int *x, int N,int input,

;>>>> ach) { ;>>>>

Data retrieval from the stack and parameter handling. We are not really concerned about this code block and it should remain unaltered.

int *acl, int*

unsigned int result; .line 4

;>>>>

N=9; LACK LARK MAR SACL .line

9 AR2,-5+LF1 *0+ *+ 6

Indirect addressing access to parameter N (N = 9). The memory location of the parameter is stored in AR2, prior to the SACL instruction.

356

TOPICS OF SPECIAL INTEREST ;>>>> LAR LAR SSXM LAC ADD ADRK SACL .line

result = *h + *x; AR3,*+ AR4,* ,AR3

* ,AR4 * ,AR2 4-LF1 * 8 ;>>>> input=2; LACK 2 SBRK 7-LF1 SACL *.line 10 ;>>>> *acl = 5; LAR AR5,*-,AR5 LACK 5 SACL * ,AR2 .line 11 ;>>>> *ach = 7; LAR AR5,* ,AR5 LACK 7 SACL * ,AR2 .line 13 ;>>>> input = 1; LACK 1 ADRK 2 SACL * ,AR1 EPI0_1: .line 14 SBRK 3 LAR AR0,*PSHD * RET .endfunc .end

Indirect addressing access to parameters * h and *x. The memory locations of the parameters are stored in AR4 (*h) and AR3 (*x), prior to the SSXM instruction. Indirect addressing access to parameter input. The memory location of the parameters is stored in AR2, prior to the SACL instruction. Indirect addressing access to parameter *acl. The memory location of the parameters is stored in AR5, prior to the SACL instruction. Indirect addressing access to parameter *ach. The memory location of the parameters is stored in AR5, prior to the SACL instruction. Indirect addressing access to parameter input. The memory location of the parameters is stored in AR2, prior to the SACL instruction. Re-populating the stack and returning. This code block should remain unaltered.

16,000000000H,2

As you may have realized already, the C compiler uses indirect addressing to assign values to the parameters of the function by copying the value from the accumulator to the appropriate memory location. Specifically, instruction SACL means, store accumulator low word. By “planting” several assignment commands inside convolve(), we forced the compiler to load auxiliary registers with the addresses of the accessed parameters, in order to copy the accumulator content to the corresponding locations. Therefore, the selected auxiliary register in SACL *, (or SACL *+, SACL *-, etc.), should be pointing to the desired parameter! Now that we have the “valuable” addresses of the function’s parameters, we may add our assembly code at the appropriate locations inside the function. As you may have guessed, h points to the kernel of a convolution, x is the input rolling buffer, N is the number of points and input is the newly sampled input. Parameters acl and ach will be used to store and return the low and high word of the result. Logically, our first 357

TOPICS OF SPECIAL INTEREST

concern would be to retrieve N and store it in a register. To do this, we simply replace the SACL instructions that stores 9 into the memory location of N, with an instruction that copies the value already stored in that memory location (i.e., N) to auxiliary register 7 (AR7). It will also be necessary to decrease AR7 by 1 (AR7 will be used as a count-down register). Generated Code ;>>>> LACK LARK MAR SACL .line

N=9; 9 AR2,-5+LF1 *0+ *+ 6

New Code ;>>>> N=9; ; REMOVED: LACK 9 LARK AR2,-5+LF1 MAR *0+ ; REMOVED: SACL *+ LAR AR7,*+, AR7; AR2 points to N, select AR7 MAR *-, AR2 ; AR2 = AR2-1, select AR2 .line 6

Here are the details: In the generated code, instruction LACK 9 loads the accumulator with number 9 (immediate addressing), with the apparent intention of copying it to N. In the next instruction, LARK -5+LF1, AR2 is loaded with the constant -5+LF1 (immediate addressing). This constant is probably an offset that only concerns the C compiler and we need not to be very worried much about. The next instruction, MAR *+ is the one that adds the missing base to AR2 in order to finally form the address of N. MAR means modify auxiliary register; it simply performs what the indirect addressing operand tells it to do, which, in this case, is to increase the content of AR2 by the content of AR0. In short, the address of N is now stored in AR2. Our task is to “snatch” its content and copy it to AR7. To do that, the LACK and SACL instructions will be removed and following MAR *+, we will load AR7 with N, using instruction, LAR AR7, *+, AR7. LAR means, load auxiliary register; it copies the content of the memory location pointed to by the selected auxiliary register (i.e., AR2) to the auxiliary register used as first operand (i.e., AR7). Notice the use *+ operand, used to increase the content of AR2. This is done because the C compiler had the same increment of AR2 in the SACL *+ instruction that we just removed. It appears that AR2 is used by the C compiler to address all non-pointer parameters in the function and there is a pre-defined chain of changes in its values; disrupting this sequence may cause addressing problems in other parts of the function. Generally, whenever you are replacing compiler-generated code, the new code should leave the auxiliary registers in the same state as the original code did, unless you are absolutely certain that this particular register will not be accessed again until the end of the function. The last added instruction is MAR *-, which decreases the content of AR7 by 1. This is necessary, since we will be using AR7 as a counter for the summation loop that follows. We now have N-1 stored in AR7, and we can move on to make extensive additions in the section that adds *h with *x, with their addresses stored in AR4 and AR3 respectively. This is where the weighted sum routine will be added. Before making any modifications, it would 358

TOPICS OF SPECIAL INTEREST

be helpful to take a quick look at the instructions of the LF2407A related to iterative multiplication and accumulation. The most common piece of assembly code to implement a convolution is usually a variation of the following: MPYK #0 ; ZERO Product register ZAC ; ZERO Accumulator SPM #0 ; No Product Shifting prior to addition RPT N ; Repeat N+1 times MACD #PMA, *; 1)ACC = ACC + [AR]*[PMA]. ; 2)[AR+1] = [AR]. 3)AR = AR-1 and PMA = PMA + 1.

Let us take each instruction separately: Assume that AR is the currently selected auxiliary register and, at the beginning of the loop, it points at the penultimate value stored in the input rolling buffer. Instruction MPYK means, multiply product register with short immediate constant; therefore, MPYK 0 is a multiplication of the product register with 0; in other words, a nice way of initializing PREG to zero. Instruction ZAC means zero the accumulator, which will also set the content of the accumulator to zero. Instruction SPM (seen earlier) loads the PM bits in the status register; SPM 0 will load PM with zero and therefore, there will not be any shifts of the product prior to addition with the content of the accumulator. The next instruction is RPT N; this instruction repeats the next instruction (i.e., MACD) N+1 times. Finally, MACD is the instruction that does it all! It multiplies two signed numbers, accumulates the previous product, shifts the data from the memory location pointed-to by the auxiliary register to the next higher location, increases the program counter and decreases (in this case) the selected auxiliary register. This is a power pack! It performs exactly the tasks required by convolution and all in one instruction! There are certain prerequisites here though: The most important is that the kernel MUST be stored in program memory; MACD uses the program counter to incrementally index the coefficients, starting from address #PMA. The second prerequisite, yet important as well, is that the rolling buffer (indexed by the selected auxiliary register) MUST reside in on-chip data memory B0 or B1. Well, number 2 is fine with us (recall that we mapped sysmem to B1), but number 1 is a problem. Our coefficients are located in data memory, since they are stored in an array. Fortunately, there are ways to work around this problem, but it will take a bit more code than we expected. Except MACD, the LF2407A has a variety of similar instructions to accommodate the calculation of weighted sums, such as MAC, MADD, MADS, etc. Unfortunately, most of them work with coefficients stored in program memory and this is a restriction for us. There is, however, instruction MPYA, which multiplies the contents of the temporary register with the contents of the memory location pointed to by the selected auxiliary register and adds the previous value of PREG in the accumulator. It is the only instruction of type multiply359

TOPICS OF SPECIAL INTEREST

accumulate that could serve our needs. Unlike MACD, MPYA does not copy the rolling buffer value to the immediate higher memory location and therefore we will have to add our own code to perform buffer shifting. Moreover, the coefficient should be loaded in TREG prior to multiplication with an LT instruction. Consequently, since we will be adding a few instructions in the loop, we will not be able to use RPT; the next best alternative is to use an auxiliary register as counter (recall that we stored N-1 in AR7) and a conditional branch BANZ instruction to implement the loop. The overall modification should be the following: Old Code ;>> result = *h+*x; LAR AR3,*+ LAR AR4,* ,AR3 SSXM LAC * ,AR4 ADD * ,AR2 ADRK 4-LF1 SACL * .line 8

New Code ;>>>>

result = *h + *x; LAR AR3,*+ LAR AR4,* ,AR3 ; [AR4] = h[N-1] and [AR3] = x[N-1] ; REMOVED: SSXM ; REMOVED: LAC *, AR4 ; REMOVED: ADD *, AR2 ; ******** Prepare loop ******** MPYK 0 ZAC SPM 0

Sum calculation for terms 1 through N-1 (h[1]*x[1]+..+h[N-1]*x[N-1]).

LOOP:

; ZERO the Product register ; ZERO the accumulator ; NO Product Shifts

; ******* Summation Loop ******* MAR *, AR3 ; Select AR3 MAR *; AR3 = AR3-1 LT *, AR4 ;TREG = x[N-n+1]. Select AR4 MPYA *-, AR3 ;ACC = ACC + PREG. ;PREG=[AR4]*TREG. Select AR3 DMOV *, AR7 ;[AR3+1] = [AR3]. Select AR7 BANZ LOOP,*- ; if AR7>0, goto LOOP MAR *, AR2 ; Select AR2 ;***Sum calculated for 1 to N-1. ADRK 4-LF1 ; REMOVED: SACL * .line 8

The C compiler has conveniently stored the addresses of *h and *x in AR4 and AR3 respectively. We remove the instructions that perform the addition (SSXM, LAC, ADD and SACL) and add the code that sums the products of h[k] and x[k] for k = 1,..,N-1. During initialization, the product register, the accumulator and the product shift bits are set to zero (MPYK 0, ZAC and SPM 0 instructions). In the first two steps of the iteration, AR3 is selected (MAR *, AR3) and decreased (MAR *-) immediately afterwards. The content of 360

TOPICS OF SPECIAL INTEREST

AR3 is then loaded in TREG (LT *, AR4) and AR4 becomes the selected auxiliary register. The next instruction (MPYA *-, AR3), adds the previous PREG value (i.e., zero) to the accumulator and then multiplies the content of the address pointed to by AR4 with TREG, stores the result in the product register, decreases AR4 and selects AR3. Instruction DMOV *, AR7 will then copy the content of the memory location pointed to by AR3 to the next higher memory location (AR3+1) and select AR7 for the conditional branch that follows. At the last instruction of the loop, BANZ LOOP, *- will branch to LOOP, if AR7 is greater than zero and perform a decrement by 1 to its content. Notice instruction MAR *, AR2 selecting AR2 as the current auxiliary register immediately after the loop. Recall that the C compiler uses AR2 to address non-pointer parameters in the function through a series of increments and decrements that we should not be interfering with. Since AR2 was the selected auxiliary register by the compiler at the end of this section (through removed instruction, ADD *, AR2), this is what we should also be doing following code additions, hence the MAR *, AR2 instruction. At this point, we have the sum of h[2]x*2++…+h[N-1]x[N-1] stored in the accumulator and the product of h[1] and x[1] stored in the product register (recall that MPYA accumulates the previous product, NOT the newest one). We still need to calculate the product of h[0] with the new input and add it to the accumulator; let aside for now, that we need to copy the new input in the first position of the rolling buffer. We can do this at the next code section, in which, the input parameter is accessed. In the section that accesses parameter input for a value assignment, we have a great opportunity to use the address stored in AR2 in order to load the input in TREG and perform the last multiplication required for the sum. Also, the new input value should be copied to the first position of the rolling buffer. Old Code ;>>>> input=2; LACK 2 SBRK 7-LF1 SACL *.line 10

New Code ;>>>> input=2; ; REMOVED: LACK 2 SBRK 7-LF1 ; REMOVED: SACL *LT *-, AR4 ; TREG = input. AR2=AR2-1. ; Select AR4 MPYA * ; ACC = ACC + PREG. ; PREG = h[0]*iput MPYA *, AR2 ;ACC = ACC+PREG.Select AR2. ;Don’t care about new PREG .line 10

The C compiler loads the accumulator with an immediate constant (LACK 2), with the obvious intention of copying it to the memory location of input, using indirect addressing mode via the 361

TOPICS OF SPECIAL INTEREST

content of AR2 (SACL *-). This is our chance to use the content of AR2 in order to load the new input to TREG. The LACK and SACL instructions are removed and the content of the address pointed to by AR2 is loaded to TREG (LT *-, AR4), while AR4 becomes the new selected register; again, the decrement is necessary because the removed SACL instruction was also performing it. The next task is to multiply TREG with the address pointed to by AR4 (which should be coefficient h[0]) using instruction MPYA *. Now, we have the sum of all terms, h*1+x*1++…+h*N-1]x[N-1] stored in the accumulator and term h[0] input stored in PREG. In order to accumulate the last product, we simply us one more multiplication instruction (MPYA *, AR2), which also selects AR2 as the current auxiliary register. Following the last MPYA, the accumulator should contain the entire sum (h[0]∙input+…+h*N-1x[N-1]); the content of PREG is irrelevant. The result is now stored and ready in the accumulator, but we need to return it somehow; this is done through parameters ach and acl, accessed for value assignment in the next two sections. Recall that the accumulator is 32-bit long and therefore, we need two separate words to store it, acl for the low word and ach for the high word. This is going to be the easiest of all! In the two following sections, we simply remove the LACK instructions and use SACL and SACH to store the low and high word respectively. Old Code

New Code

;>>>> *acl = 5; LAR AR5,*-,AR5 LACK 5 SACL * ,AR2 .line 11

;>>>> *acl = 5; LAR AR5,*-,AR5 ;REMOVED: LACK 5 SACL * ,AR2 .line 11

;>>>> *ach = 7; LAR AR5,* ,AR5 LACK 7 SACL * ,AR2 .line 13

;>>>> *ach = 7; LAR AR5,* ,AR5 ;REMOVED: LACK 7 ;REMOVED: SACL *, AR2 SACH * ,AR2 .line 13

We are almost done! Now the result is stored in the memory locations related to pointers acl and ach. The only remaining task is to copy the input in the first position of the rolling buffer. The next code section marked for modifications concerns the assignment of 1 to parameter input. We can now copy the content of the address pointed to by AR2 (which should be the value of input) to the memory location pointed to by AR3 (which should be the first position in the rolling buffer. This is also fairly simple. The relative modifications are shown below: 362

TOPICS OF SPECIAL INTEREST

Old Code

New Code

;>>>> input = 1; LACK 1 ADRK 2 SACL * ,AR1

;>>>> input = 1; ;REMOVED: LACK 1 ADRK 2 ;AR2 = AR2 + 2 (compiler offset) LACL *, AR3 ;ACC = [AR2]. Select AR3 SACL *, AR1 ;[AR3] = ACC. Select AR1

The initial instruction that loads the accumulator with 1 (LACK 1) is removed. Fortunately, SACL *, AR1 is exactly what we need and therefore, we keep it. Notice the ADRK 2 instruction: It adds an immediate 2 to the content of the selected auxiliary register (i.e., AR2) in order to form the address of input. This addition is part of the chain of increments/decrements that the C compiler performs on AR2 throughout the code in order to access the parameters of the function; this is yet another case that goes to show that these changes are very difficult to follow and you should not interfere, unless you are very sure of what you are doing. This is it! We only have to clear the code from unnecessary comments, change the file name in the .file directive at the top (name and save it as “convasm.asm”), add it to the project and remove the original “func1.c”. To test the function, initialize the kernel to {1, 1, 1} for a simple moving average filter and the input buffer to {4, 3, 2}. Now, modify the calling parameters of convolve() as follows: Address of last element

N-1

Referenced parameters to store low and high word of the result.

convolve(&h[2], &x[2], 2, 5, &acl, &ach); New input

Since the sum starts at the end of the kernel and rolling buffer, you should pass the addresses of the last elements of h and x (i.e., &h[2] and &x[2]). Also, if N is the length of the kernel, then you should pass N-1 (i.e., 2). The new input value is 5, which should give us a result of 12 stored in acl and ach. The new code for the main function should be the following: extern void convolve(int*, int*, int, int, int*, int*); void main(void) { int h[] = {1, 1, 1}; int x[] = {4, 3, 2}; int ach, acl;

363

TOPICS OF SPECIAL INTEREST

convolve(&h[2], &x[2], 2, 5, &acl, &ach); while (1) { } }

The final version of “convasm.asm” should now be: .port .file "convasm.asm" .text .sym _convolve,_convolve,32,2,0 .globl _convolve .func 3 ****************************************************** * FUNCTION DEF : _convolve ****************************************************** _convolve: LF1

;>>>> ;>>>> ;>>>>

;>>>>

.set

0

POPD SAR SAR LARK LAR

*+ AR0,*+ AR1,* AR0,2 AR0,*0+,AR2

.sym _h,-3+LF1,20,9,16 .sym _x,-4+LF1,20,9,16 .sym _N,-5+LF1,4,9,16 .sym _input,-6+LF1,4,9,16 .sym _acl,-7+LF1,20,9,16 .sym _ach,-8+LF1,20,9,16 .sym _result,1,14,1,16 .line 1 void convolve(int *h, int *x, int N,int input, unsigned int result; .line 4 N=9; LARK AR2,-5+LF1 MAR *0+ LAR AR7,*+, AR7 ; AR7 = N. Select AR7 MAR *-, AR2 ; AR7 = AR7 -1. Select AR2 .line 6 result = *h + *x; LAR AR3,*+ LAR AR4,* ,AR3

int *acl, int* ach) {

364

TOPICS OF SPECIAL INTEREST ; [AR4] = last coefficient and [AR3] = oldest input in rolling buffer ; ******************* Preparing Sum ***************************** MPYK 0 ; ZERO the Product register ZAC ; ZERO the accumulator SPM 0 ; PM = 0 (No product shifting) LOOP:

MAR *, AR3 ; Select AR3 MAR *; AR3 = AR3 - 1 LT *, AR4 ; TREG = [AR3]. Select AR4 MPYA *-, AR3 ; ACC=ACC+PREG. PREG=TREG*[AR4]. AR4=AR4-1. Select AR3 DMOV *, AR7 ; [AR3+1] = [AR3]. Select AR7 BANZ LOOP,*-

; if AR7>0, goto LOOP. AR7 = AR7 - 1

MAR *, AR2 ; Select AR2 ;********************************************************************** ADRK 4-LF1 .line 8 ;>>>> input=2; SBRK 7-LF1 LT *-, AR4 ; TREG = [AR2]. Select AR4 MPYA * ; ACC = ACC + PREG. PREG = [AR4]*TREG. MPYA *, AR2 ; ACC = ACC + PREG. New PREG is "don't care". Select AR2 .line 10 ;>>>> *acl = 5; LAR AR5,*-,AR5 SACL * ,AR2 ; [AR5] = Low Word(ACC). Select AR2 .line 11 ;>>>> *ach = 7; LAR AR5,* ,AR5 SACH * ,AR2 ; [AR5] = High Word(ACC). Select AR2 .line 13 ;>>>> input = 1; ADRK 2 LACL *, AR3 ; ACC = [AR2]. Select AR3 SACL *, AR1 ; [AR3] = ACC. Select AR1 ; accumulator stored. go about usual business EPI0_1: .line 14 SBRK 3 LAR AR0,*PSHD * RET .endfunc .end

16,000000000H,2

The new project view window, after you have removed “func1.c” and added “convasm.asm”, should look like the one shown in Figure 12-7. Make sure you saved the changes in the project as well.

365

TOPICS OF SPECIAL INTEREST

Figure 12-7. Project view for “AsmFunc.pjt”.

Build the code, connect to the DSP and place a breakpoint on the call to convolve() in the main function as shown in Figure 12-8.

Figure 12-8. Breakpoint on the call to convolve() in main().

Now, load the program, reset the CPU and run. In case you are worried about the watchdog, the breakpoint will prevent it from forcing a reset. Execution should suspend exactly before entering convolve(). Hit F11 to step through the function. If all goes well, you should be transferred to “convasm.asm” for the next steps. Continue step-by-step execution until you have reached main() again. Open a watch window and observe x, acl and ach. Now, acl and ach should be 12 and 0 respectively, while the input buffer should have shifted to the right with the recent input value (i.e., 5) stored in the first position (Figure 12-9).

Figure 12-9. Watch window following execution of convolve().

366

TOPICS OF SPECIAL INTEREST

In terms of performance, the new function is incomparably superior to the convolution C routine that we used in Chapter 11. However, there are certain slight “deficiencies”. First, the assembly routine works only with integers and not floating point numbers. Secondly, we can only use on-chip memory blocks for the sysmem (heap) section, which is not a bad thing necessarily, but it confines the program data to a limited memory space. Both problems can be worked-around, but with rather extensive assembly code. Either way, the result will present significantly higher performance than the corresponding implementation in C. To be fair with the DSP, it was specifically designed for very small, optimized and specialized applications; within this context, the 2407A is a real champion! However, it simply is not cut “for” creating extensive multi-purpose code with plenty of functions and huge data structures. 12.1.4 A few Tips for your C-callable Assembly Template The best way to “direct” the C compiler towards producing the desired code sections is to “plant” value-assignment statements inside the function, exactly at the points you are intending to make use of a parameter for calculations. Also, avoid regular parameters and try to use pointers either by value, or reference. The C compiler will normally assign specific auxiliary registers directly to the pointer and therefore it would be easier to recognize the connection inside the generated assembly code. On the other hand, regular parameters are usually addressed directly and not with specific auxiliary registers, but rather with a series of additions and subtractions “spread” throughout the entire function using a single auxiliary register (i.e., AR2). You may specify one or two regular parameters; a higher number will, most likely, create very confusing assembly code. In this context, following the addition of your code in a section, try to leave the auxiliary registers in the same state left by the original code at the end. Finally, should you wish to work with two or more parameters in a section, the best way to prepare it, is to use an assignment statement containing all of these parameters (e.g., x = a + b + c).

12.2 More about the Watchdog Timer Throughout the examples in this text, the watchdog was faced more-less like a demon lurking in the dark, trying to ruin our good efforts to execute a program. This is true, especially when trying to develop an idea for the first time and safety precautions are not a priority. However, the watchdog can become a guardian-angel of the integrity of a system against unexpected or unforeseen circumstances, such as power surges, bus failures, etc. All in all, programs are man-made and are bound to fail from time to time. The watchdog is a clean-cut solution to all these circumstances that we may have overlooked during development and testing, and it is absolutely necessary in the commercial version of the software, in order to prevent those flaws from disrupting the DSP in any way. 367

TOPICS OF SPECIAL INTEREST

12.2.1 Overview of the Watchdog Timer The watchdog is yet another peripheral on the LF2407A. It essentially comprises an 8bit counter and a prescale counter, with its own internal clock (WDCLK) as a subdivision of the CPU clock (WDCLK = CPUCLK/512). If enabled, the counter constantly counts up until it has been reset by software, or until it overflows. Upon an overflow, the watchdog will assert a system reset. It can be easily inferred that the watchdog is mainly a safety mechanism against unexpected “hangs” (i.e., never-ending loop locks).

Figure 12-9. Block diagram of the WD module3.

There are not many registers to deal with here and all of them are 8-bit long: The watchdog counter register (WDCNTR), the watchdog key register (WDKEY) and the watchdog control register (WDCR). Until now, we have been disabling the watchdog in our programs. In

3

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

368

TOPICS OF SPECIAL INTEREST

the next example, we will be enabling it and ensuring that it gets serviced at regular time intervals before the counter overflows. 12.2.2 Using the Watchdog in a Program Create a new folder named “WD” and copy all appropriate files to use the event managers and interrupts, along with the system initialization routine (“DSP24_Sys.c”), a linker command file and “cvectors.asm”. Create a new project named “WD” and add the files to it. In this example, the watchdog will be enabled and servicing will be taking place inside the timer 1 period interrupt service routine. In the system initialization routine initSystem(), make the highlighted changes to the configuration of WDCR: #define #define #define #define

WDCR SCSR1 SCSR2 INTMASK

(volatile (volatile (volatile (volatile

unsigned unsigned unsigned unsigned

int int int int

*)0x7029 *)0x7018 *)0x7019 *)0x0004

#define WSGR portFFFF ioport unsigned int portFFFF; void initSystem(void) { *SCSR1 = 0x0FE; /* bit 15 bit 14 bit 13-12 bit 11-9 bit 8 bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 */

0: 0: 00: 000: 0: 1: 1: 1: 1: 1: 1: 0: 1:

reserved CLKOUT = CPUCLK IDLE1 selected for low-power mode PLL x4 mode reserved 1 = enable ADC module clock 1 = enable SCI module clock 1 = enable SPI module clock 0 = enable CAN module clock 0 = enable EVB module clock 1 = enable EVA module clock reserved clear the ILLADR bit

*SCSR2 = (*SCSR2 | 0x000B) & 0x000F; /* bit 15-6 0's: reserved bit 5 0: do NOT clear the WD OVERRIDE bit bit 4 0: XMIF_HI-Z, 0=normal mode, 1=Hi-Z'd bit 3 1: disable the boot ROM, enable the FLASH bit 2 no change MP/MC* bit reflects state of MP/MC* pin bit 1-0 11: 11 = SARAM mapped to prog and data

369

TOPICS OF SPECIAL INTEREST */ *WDCR = 0x00AA; /* bits 15-8 0's: bit 7 1: bit 6 0: bit 5-3 101: bit 2-0 010: */ WSGR = 0x0040; /* bit 15-11 0's: bit 10-9 00: bit 8-6 001: bit 5-3 000: bit 2-0 000: */ *INTMASK = 0;

reserved clear WD flag enable the dog must be written as 101 reset at 6.6 ms

Enable the Watchdog (WDDIS = 0) with a /x2 WDCLOCK prescale (overflow at 6.6 ms).

reserved bus visibility off 1 wait-state for I/O space 0 wait-state for data space 0 wait state for program space // mask core interrupts

}

Recall from Chapter 2, that WDCR (Figure 12-10) enables/disables the watchdog and configures the count-up clocking signal prescaling with bits 0, 1 and 3 (WDPS0, WDPS1 and WDPS2). Assigning a value of 2 (010b) to the WDPS bits will result in a count-up step frequency of,

; therefore, since WDCNTR is 8-bit long, overflow

should occur every 256/39 6.6 ms. Table 12-2 shows the prescale selections for the watchdog count-up step (overflow frequency and time are calculated based on a 40 MHz CPU clock frequency).

Table 12-2. Watchdog timer prescale bit selections4.

4

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

370

TOPICS OF SPECIAL INTEREST

Figure 12-10. Watchdog control register (WDCR)5.

Recall that any access to WDCR must also assign a “101” value to the watchdog check bits, otherwise a system reset will be asserted. This is why we cannot use a C structure to access the WDCR bits individually. Any attempt to do so will cause a system reset, since the check bits must always be written to with “101”, if any other bits are to be accessed in the register. The check bits are essentially a measure of protection from accidental changes to WDCR by the software. Finally, the WDDIS and WDFLAG bits are set to “1” to enable the watchdog and clear the watchdog flag. In our example, the watchdog counter must be reset periodically, within a time interval less than 6.6 ms from the previous reset; otherwise, it will cause a system reset. To reset the counter, we simply need to write a 0x55 and 0xAA values to the watchdog key register (Figure 12-11) in consecutive accesses in the exact given order (i.e., 0x55 must be written first, followed by 0xAA), otherwise the counter will not reset. Writing any other value to WDKEY will result in a system reset.

Figure 12-11. Watchdog reset key register (WDKEY)5.

In the next example, we will be using timer 1 to service the watchdog upon a period interrupt. Create a file named “WDmain.c” and type the following code: #include "DSP24_Ev.h" #include "DSP24_Core.h" extern initSystem(void); void initTimer(); 5

TMS320LF/LC240xA DSP Controllers Reference Guide – System and Peripherals (Texas Instruments)

371

TOPICS OF SPECIAL INTEREST

void main() { initSystem(); initTimer(); // enable interrupts asm(" CLRC INTM"); // main loop while (1) { } } void initTimer(void) { GPTCONAbits->bit.TCOMPOE=0; // disable all GP Timer Compare outputs GPTCONAbits->bit.T1TOADC=00; // Do not start ADC with Timer1 GPTCONAbits->bit.T2TOADC=00; // Do not start ADC with Timer2 // Timer 1 Initialization *T1CNT = 0; *T1PR = 48000; // 1.2 ms timer period T1CONbits->all = 0; T1CONbits->bit.TCMPREN = 0; // Disable Timer Compare T1CONbits->bit.TCLD = 0; // (no effect) T1CONbits->bit.TMODE = 2; // Continuous Up/Down mode T1CONbits->bit.TPS = 0; // Input Clock PreScale /x1 T1CONbits->bit.TCLKS = 0; // Internal Clock source select T1CONbits->bit.soft = 0; T1CONbits->bit.free = 0; // immediate stop on emulation suspend // enable Timer 1 Period interrupt EVAIFRAbits->bit.T1PINT = 1; // clear T1PINT flag EVAIMRAbits->bit.T1PINT = 1; // enable interrupt // enable core interrupt INT 2 (timer 1) *IMR |= 2; // start the timer T1CONbits->bit.TENABLE = 1; // Enable Timer 1 }

Timer 1 is configured for a period interrupt every 1.2 ms. It should be more than enough to prevent the watchdog from causing a system reset at any point (recall that with the 372

TOPICS OF SPECIAL INTEREST

chosen prescale setting, the watchdog will wait for about 6.6 ms before asserting a system reset). Reset of the counter occurs upon the period interrupt of timer 1; therefore, the corresponding ISR code should be the following: interrupt void T1PINT_ISR(void) { unsigned int *WDKEY = (void*)0x7025; // Service the watchdog *WDKEY = 0x55; *WDKEY = 0xAA; // clear interrupt flag EVAIFRAbits->bit.T1PINT = 1; }

Build and download the project. Set a breakpoint inside the main() function as shown in Figure 12-12.

Figure 12-12. Breakpoint inside main().

If you now hit run, execution will stop at the breakpoint. If you continue execution and all goes well, the DSP should not stop again at the breakpoint (meaning that the watchdog is serviced in time and no system reset occurs). For the sake of experimentation, change the configuration of the timer for a /x8 clock prescaling factor: T1CONbits->bit.TPS = 3;

The period of the timer will now become 8x1.2 ms = 9.6 ms; this is plenty of time for the watchdog counter to overflow. If you now rebuild, download and repeat the previous process, you should be having execution stops at the breakpoint every time you try to execute. Servicing the watchdog timer is a very simple task. The difficult part is to figure out where exactly to place the WDKEY reset code assignments (0x55 and then, 0xAA) in the program. A general rule would be to service the watchdog following iterative processes which may be a liability to the application (e.g., routines that perform handshaking in communications). 373

Bibliography

Lovrich, A., et al., 1987. Digital Signal Processing Applications with the TMS320 Family, Volume 1. Englewood Cliffs, New Jersey: Prentice-Hall Inc. Toliyat, H., Campbell, S., 2004. DSP-Based Electromechanical Motion Control. Boca Raton, Florida: CRC Press, LLC. Smith, S. W., 1997. The Scientist and Engineer’s Guide to Digital Signal Processing. San Diego, California: California Technical Publishing. Oppenheim, A. V., Willsky, A. S. and Nawab, S. H., 1997. Signals & Systems. Upper Saddle River, New Jersey: Prentice-Hall Inc. Ogata, K., 1995. Discrete Time Control Systems. Upper Saddle River, New Jersey: Prentice-Hall Inc. Reese, R., Bruce, J. and Jones, B., 2009. Microcontrollers. From Assembly Language to C Using the PIC24 Family. Boston, Massachusetts: Course Technology. Douglas, F. E., 1987. Handbook of Digital Signal Processing – Engineering Applications. Anaheim, California: Academic Press, Inc.

374

Sources

Texas Instruments, 2006. TMS320LF/LC240xA DSP Controllers Reference Guide - System and Peripherals. [online] Dallas, Texas: Texas Instruments Incorporated. Available at: [Accessed 26 October 2009]. Texas Instruments, 1999. TMS320LF/LC240xA DSP Controllers Reference Guide – CPU and Instruction Set. [online] Dallas, Texas: Texas Instruments Incorporated. Available at: [Accessed 10 November 2009]. Texas Instruments, 2003.TMS320LF2407, TMS320LF2406, TMS320LF2402 DSP Controllers. [online] Dallas, Texas: Texas Instruments Incorporated. Available at: [Accessed 21 January 2010]. Anderson, T., 2001.An Easy Way of Creating a C-callable Assembly Function for the TMS320F28x DSP. [online] Dallas, Texas: Texas Instruments Incorporated. Available at: [Accessed 18 October 2009]. Brenman, L., 1995.Setting Up TMS320 Interrupts in C. [online] Dallas, Texas: Texas Instruments Incorporated. Available at: [Accessed 6 December 2009]. Alter, D. A., 2002. Getting Started in C and Assembly Code with the TMS320LF24x DSP. [online] Dallas, Texas: Texas Instruments Incorporated. Available at: [Accessed 6 October 2009]. Spectrum Digital, 2003.eZdsp™ LF2407A – Technical Reference. [online] Stafford, Texas: Spectrum Digital Incorporated. Available at: [Accessed 10 October 2009]. Texas Instruments, 2000.TLC5615, TLC5615I 10-Bit Digital to Analog Converters. [online] Dallas, Texas: Texas Instruments Incorporated. Available at: [Accessed 10 December 2010].

375

Texas Instruments, 2010. ISO1050 Isolated CAN Transceiver. [online] Dallas, Texas: Texas Instruments Incorporated. Available at: [Accessed 5 December 2010]. CAN in Automation, 2010.CAN Protocol Specification. [online] Nuremberg: CAN in Automation. Available at: [Accessed 27 November 2010]. Softing AG, 2010.CAN bus (Controller Area Network), an overview. [online] Softing AG. Available at: [Accessed 27 November 2010]. Bosch, R., 1991.CAN Specification. [online] Stuttgart: Bosch. Available at: [Accessed 29 November 2010]. Wikipedia, 2010. Cooley-Tukey FFT Algorithm. [online] Wikipedia. Available at: [Accessed 27 November 2010]. Jones, D., 2004. FIR Filter Structures. [online] Connexions. Available at: [Accessed 2 December 2010]. Jones, D., 2004. IIR Filter Structures. [online] Connexions. Available at: [Accessed 17 December 2010]. Vandevenne, L., 2007.Lode’s Computer Graphics Tutorial – Fourier Transform. [online] Lode Vandevenne. Available at: [Accessed 2 December 2010]. Bores, C., 2010. Introduction to DSP – IIR Filters. [online] Woking, Surrey: Bores Signal Processing. Available at: [Accessed 2 December 2010]. Wikipedia. Butterworth Filter. [online] Wikipedia. Available at: [Accessed 21 December 2010].

376

377

APPENDIX

A

Header and C files for EVA, EVB

Structure Definitions (“DSP24_Ev.h”) #ifndef DSP24_EV_H #define DSP24_EV_H // GPTCONA struct GPTCONA_BITS { unsigned int T1PIN:2; unsigned int T2PIN:2; unsigned int rsvd1:2; unsigned int TCOMPOE:1; unsigned int T1TOADC:2; unsigned int T2TOADC:2; unsigned int rsvd2:2; unsigned int T1STAT:1; unsigned int T2STAT:1; unsigned int rsvd3:1;

// // // // // // // // // //

0-1 Polarity of GP timer 1 compare 2-3 Polarity of GP timer 2 compare 4-5 Reserved 6 CoMPare Output Enable) 7-8 Start ADC with Timer 3 9-10 Start ADC with Timer 4 11-12 Reserved 13 Timer 3 STATus 14 Timer 3 STATus 15 reserved

}; union GPTCONA_REG { unsigned int all; struct GPTCONA_BITS bit; }; extern volatile union GPTCONA_REG *GPTCONAbits; // GPTCONB struct GPTCONB_BITS { unsigned int T3PIN:2; // 0-1 Polarity of GP timer 3 compare unsigned int T4PIN:2; // 2-3 Polarity of GP timer 4 compare unsigned int rsvd1:2; // 4-5 Reserved unsigned int TCOMPOE:1; // 6 CoMPare Output Enable unsigned int T3TOADC:2; // 7-8 start of ADC with T3 unsigned int T4TOADC:2; // 9-10 start of ADC with Timer4 unsigned int rsvd2:2; // 11-12 reserved unsigned int T3STAT:1; // 13 Timer 3 Status unsigned int T4STAT:1; // 14 Timer 4 Status unsigned int rsvd3:1; // 15 reserved }; union GPTCONB_REG { unsigned int all; struct GPTCONB_BITS bit; };

378

extern volatile union GPTCONB_REG *GPTCONBbits; // T1CON struct T1CON_BITS unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int

{ rsvd1:1; // 0 Reserved TCMPREN:1; // 1 Timer Compare Enabled TCLD:2; // 2-3 Compare register reload condition TCLKS:2; // 4-5 Clock Source Select TENABLE:1; // 6 Timer Enable rsvd2:1; // 7 Reserved TPS:3; // 8-10 Input Clock Prescaling TMODE:2; // 11-12 Count mode selection rsvd3:1; // 13 reserved SOFT:1; // 14 EMUlation control bit 0 (soft) FREE:1; // 15 EMUlation control bit 1 (free)

}; union T1CON_REG { unsigned int all; struct T1CON_BITS bit; }; extern volatile union T1CON_REG *T1CONbits; //T2CON struct T2CON_BITS unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int

{ T1PERSEL:1; // 0 Select Period register TCMPREN:1; // 1 Timer Compare Enables TCLD:2; // 2-3 Compare register reload condition TCLKS:2; // 4-5 Clock Source Select TENABLE:1; // 6 Timer Enable T2SWT1:1; // 7 if 1, Timer2 starts with Timer1 TPS:3; // 8-10 Input Clock Prescaling TMODE:2; // 11-12 Count mode selection rsvd3:1; // 13 reserved SOFT:1; // 14 EMUlation control bit 0 (soft) FREE:1; // 15 EMUlation control bit 1 (free)

}; union T2CON_REG { unsigned int all; struct T2CON_BITS bit; }; extern volatile union T2CON_REG *T2CONbits; //T3CON struct T3CON_BITS unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int

{ rsvd1:1; // 0 Select Period register TCMPREN:1; // 1 Timer Compare Enabled TCLD:2; // 2-3 Compare register reload condition TCLKS:2; // 4-5 Clock Source Select TENABLE:1; // 6 Timer Enable rsvd2:1; // 7 reserved

379

unsigned unsigned unsigned unsigned unsigned

int int int int int

TPS:3; TMODE:2; rsvd3:1; SOFT:1; FREE:1;

// // // // //

8-10 Input Clock Prescaling 11-12 Count mode selection 13 reserved 14 EMUlation control bit 0 (soft) 15 EMUlation control bit 1 (free)

}; union T3CON_REG { unsigned int all; struct T3CON_BITS bit; }; extern volatile union T3CON_REG *T3CONbits; //T4CON struct T4CON_BITS unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int

{ T3PERSEL:1; // 0 Select Period register TCMPREN:1; // 1 Timer Compare Enabled TCLD:2; // 2-3 Compare register reload condition TCLKS:2; // 4-5 Clock Source Select TENABLE:1; // 6 Timer Enable T4SWT3:1; // 7 if 1, Timer4 starts with Timer3 TPS:3; // 8-10 Input Clock Prescaling TMODE:2; // 11-12 Count mode selection rsvd3:1; // 13 reserved SOFT:1; // 14 EMUlation control bit 0 (soft) FREE:1; // 15 EMUlation control bit 1 (free)

}; union T4CON_REG { unsigned int all; struct T4CON_BITS bit; }; extern volatile union T4CON_REG *T4CONbits; // EVAIMRA struct EVAIMRA_BITS { unsigned int PDPINTA:1; unsigned int CMP1INT:1; unsigned int CMP2INT:1; unsigned int CMP3INT:1; unsigned int rsvd1:3; unsigned int T1PINT:1; unsigned int T1CINT:1; unsigned int T1UFINT:1; unsigned int T1OFINT:1; unsigned int rsvd2:5; };

// // // // // // // // // //

0 1 2 3 4-6 7 8 9 10 11-15

Enable PDPINTA Enable Enable Enable reserved Enable Enable Enable Enable reserved

union EVAIMRA_REG { unsigned int all; struct EVAIMRA_BITS bit;

380

}; extern volatile union EVAIMRA_REG *EVAIMRAbits; //EVAIMRB struct EVAIMRB_BITS { unsigned int T2PINT:1; unsigned int T2CINT:1; unsigned int T2UFINT:1; unsigned int T2OFINT:1; unsigned int rsvd1:12; };

// // // // //

0 1 2 3 4-15

Enable Enable Enable Enable reserved

union EVAIMRB_REG { unsigned int all; struct EVAIMRB_BITS bit; }; extern volatile union EVAIMRB_REG *EVAIMRBbits; // EVAIMRC struct EVAIMRC_BITS { unsigned int CAP1INT:1; unsigned int CAP2INT:1; unsigned int CAP3INT:1; unsigned int rsvd1:13; };

// // // //

0 Enable/Disable CAP1 interrupt 1 Enable/Disable CAP2 interrupt 2 Enable/Disable CAP3 interrupt 3-15 reserved

union EVAIMRC_REG { unsigned int all; struct EVAIMRC_BITS bit; }; extern volatile union EVAIMRC_REG *EVAIMRCbits; // EVBIMRA struct EVBIMRA_BITS { unsigned int PDPINTB:1; unsigned int CMP4INT:1; unsigned int CMP5INT:1; unsigned int CMP6INT:1; unsigned int rsvd1:3; unsigned int T3PINT:1; unsigned int T3CINT:1; unsigned int T3UFINT:1; unsigned int T3OFINT:1; unsigned int rsvd2:5; };

// // // // // // // // // //

0 1 2 3 6:4 7 8 9 10 15:11

Enable PDPINTB Enable Enable Enable reserved Enable Enable Enable Enable reserved

union EVBIMRA_REG { unsigned int all; struct EVBIMRA_BITS bit; }; extern volatile union EVBIMRA_REG *EVBIMRAbits;

381

// EVBIMRB struct EVBIMRB_BITS { unsigned int T4PINT:1; unsigned int T4CINT:1; unsigned int T4UFINT:1; unsigned int T4OFINT:1; unsigned int rsvd1:12; };

// // // // //

0 1 2 3 15:4

T4 Period int enable T4 Compare int enable T4 Underflow int enable T4 Overflow int enable reserved

union EVBIMRB_REG { unsigned int all; struct EVBIMRB_BITS bit; }; extern volatile union EVBIMRB_REG *EVBIMRBbits; // EVBIMRC struct EVBIMRC_BITS { unsigned int CAP4INT:1; unsigned int CAP5INT:1; unsigned int CAP6INT:1; unsigned int rsvd1:13; };

// // // //

0 1 2 3-15

Capture 4 int enable Capture 5 int enable Capture 6 int enable reserved

union EVBIMRC_REG { unsigned int all; struct EVBIMRC_BITS bit; }; extern volatile union EVBIMRC_REG *EVBIMRCbits; //EVAIFRA struct EVAIFRA_BITS { unsigned int PDPINTA:1; unsigned int CMP1INT:1; unsigned int CMP2INT:1; unsigned int CMP3INT:1; unsigned int rsvd1:3; unsigned int T1PINT:1; unsigned int T1CINT:1; unsigned int T1UFINT:1; unsigned int T1OFINT:1; unsigned int rsvd2:5; };

// // // // // // // // // //

0 1 2 3 4-6 7 8 9 10 11-15

Flag Flag Flag Flag reserved Flag Flag Flag Flag reserved

union EVAIFRA_REG { unsigned int all; struct EVAIFRA_BITS bit; }; extern volatile union EVAIFRA_REG *EVAIFRAbits; //EVAIFRB struct EVAIFRB_BITS { unsigned int T2PINT:1; unsigned int T2CINT:1; unsigned int T2UFINT:1;

// 0 // 1 // 2

Flag Flag Flag

382

unsigned int unsigned int

T2OFINT:1; rsvd1:12;

// 3 // 4-15

Flag reserved

}; union EVAIFRB_REG { unsigned int all; struct EVAIFRB_BITS bit; }; extern volatile union EVAIFRB_REG *EVAIFRBbits; //EVAIFRC struct EVAIFRC_BITS { unsigned int CAP1INT:1; unsigned int CAP2INT:1; unsigned int CAP3INT:1; unsigned int rsvd1:13; };

// // // //

0 Flag 1 Flag 2 flag 3-15

reserved

union EVAIFRC_REG { unsigned int all; struct EVAIFRC_BITS bit; }; extern volatile union EVAIFRC_REG *EVAIFRCbits; // EVAIFRA struct EVBIFRA_BITS { unsigned int PDPINTB:1; unsigned int CMP4INT:1; unsigned int CMP5INT:1; unsigned int CMP6INT:1; unsigned int rsvd1:3; unsigned int T3PINT:1; unsigned int T3CINT:1; unsigned int T3UFINT:1; unsigned int T3OFINT:1; unsigned int rsvd2:5; };

// // // // // // // // // //

0 1 2 3 4-6 7 8 9 10 11-15

Flag PDPINTB Flag Flag Flag reserved Flag Flag Flag Flag reserved

union EVBIFRA_REG { unsigned int all; struct EVBIFRA_BITS bit; }; extern volatile union EVBIFRA_REG *EVBIFRAbits; // EVBIFRB struct EVBIFRB_BITS { unsigned int T4PINT:1; unsigned int T4CINT:1; unsigned int T4UFINT:1; unsigned int T4OFINT:1; unsigned int rsvd1:12; }; union EVBIFRB_REG { unsigned int

// // // // //

0 1 2 3 15:4

Flag Flag Flag Flag reserved

all;

383

struct

EVBIFRB_BITS bit;

}; extern volatile union EVBIFRB_REG *EVBIFRBbits; // EVBIFRC struct EVBIFRC_BITS { unsigned int CAP4INT:1; unsigned int CAP5INT:1; unsigned int CAP6INT:1; unsigned int rsvd:13; };

// // // //

0 flag 1 flag 2 flag 3-15 reserved

union EVBIFRC_REG { unsigned int all; struct EVBIFRC_BITS bit; }; extern volatile union EVBIFRC_REG *EVBIFRCbits; // COMCONA struct COMCONA_BITS unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int };

{ rsvd1:8; // 0-7 Reserved PDPINTASTAT:1; // 8 PDPINTA pin status FCOMPOE:1; // 9 COMPare Output Enable ACTRLD:2; // 10-11 ACTRA Reload Condition SVENABLE:1; // 12 Space Vector PWM mode enable CLD:2; // 13-14 Compare register reload condition CENABLE:1; // 15 Compare Enable

union COMCONA_REG { unsigned int all; struct COMCONA_BITS bit; }; extern volatile union COMCONA_REG *COMCONAbits; // COMCONB struct COMCONB_BITS unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int };

{ rsvd1:8; // 0-7 Reserved PDPINTBSTAT:1; // 8 PDPINTA pin status FCOMPOE:1; // 9 COMPare Output Enable ACTRLD:2; // 10-11 ACTRB Reload Condition SVENABLE:1; // 12 Space Vector PWM mode enable CLD:2; // 13-14 Compare register reload condition CENABLE:1; // 15 Compare Enable

union COMCONB_REG { unsigned int all; struct COMCONB_BITS bit; }; extern volatile union COMCONB_REG *COMCONBbits;

384

// ACTRA struct ACTRA_BITS { unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int };

CMP1ACT:2; CMP2ACT:2; CMP3ACT:2; CMP4ACT:2; CMP5ACT:2; CMP6ACT:2; D0:1; D1:1; D2:1; SVRDIR:1;

// 0-1 Action on Compare Output Pin 1 // 2-3 Action on Compare Output Pin 2 // 4-5 Action on Compare Output Pin 3 // 5-7 Action on Compare Output Pin 4 // 8-9 Action on Compare Output Pin 5 // 10-11 Action on Compare Output Pin 6 // 12 Basic Space Vector bit D0 // 13 Basic Space Vector bit D1 // 14 Basic Space Vector bit D2 // Space Vector PWM rotation direction

union ACTRA_REG { unsigned int all; struct ACTRA_BITS bit; }; extern volatile union ACTRA_REG *ACTRAbits; // ACTRB struct ACTRB_BITS { unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int unsigned int };

CMP7ACT1:2; // 0-1 Action on Compare Output Pin 1 CMP8ACT1:2; // 2-3 Action on Compare Output Pin 2 CMP9ACT1:2; // 4-5 Action on Compare Output Pin 3 CMP10ACT1:2; // 5-7 Action on Compare Output Pin 4 CMP11ACT1:2; // 8-9 Action on Compare Output Pin 5 CMP12ACT1:2; // 10-11 Action on Compare Output Pin 6 D0:1; // 12 Basic Space Vector bit D0 D1:1; // 13 Basic Space Vector bit D1 D2:1; // 14 Basic Space Vector bit D2 SVRDIR:1; // Space Vector PWM rotation direction

union ACTRB_REG { unsigned int all; struct ACTRB_BITS bit; }; extern volatile union ACTRB_REG *ACTRBbits; // DBTCONA struct DBTCONA_BITS { unsigned int rsvd1:2; unsigned int DBTPS:3; unsigned int EDBT1:1; unsigned int EDBT2:1; unsigned int EDBT3:1; unsigned int DBT:4; unsigned int rsvd2:4; };

// // // // // // //

0-1 2-4 5 6 7 8-11 12-15

reserved Dead-Band Dead-Band Dead-Band Dead-Band Dead-Band reserved

timer timer timer timer timer

prescaler 1 enable 2 enable 3 enable period

union DBTCONA_REG { unsigned int all; struct DBTCONA_BITS bit; };

385

extern volatile union DBTCONA_REG *DBTCONAbits; // DBTCONB struct DBTCONB_BITS { unsigned int rsvd1:2; unsigned int DBTPS:3; unsigned int EDBT1:1; unsigned int EDBT2:1; unsigned int EDBT3:1; unsigned int DBT:4; unsigned int rsvd2:4; };

// // // // // // //

0-1 2-4 5 6 7 8-11 12-15

reserved Dead-Band Dead-Band Dead-Band Dead-Band Dead-Band reserved

timer timer timer timer timer

prescaler 1 enable 2 enable 3 enable period

union DBTCONB_REG { unsigned int all; struct DBTCONB_BITS bit; }; extern volatile union DBTCONB_REG *DBTCONBbits; // CAPCONA struct CAPCONA_BITS { unsigned int rsvd1:2; unsigned int CAP3EDGE:2; unsigned int CAP2EDGE:2; unsigned int CAP1EDGE:2; unsigned int CAP3TOADC:1; unsigned int CAP12TSEL:1; unsigned int CAP3TSEL:1; unsigned int rsvd2:1; unsigned int CAP3EN:1; unsigned int CAP12EN:2; unsigned int CAPRES:1; };

// // // // // // // // // // //

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

reserved Edge Detection for Unit 3 Edge Detection for Unit 2 Edge Detection for Unit 1 Unit 3 starts the ADC GP Timer select for Units 1 and 2 GP Timer selct for Unit 3 reserved Capture Unit 3 control Capture Unit 1 and 2 enable Capture reset

union CAPCONA_REG { unsigned int all; struct CAPCONA_BITS bit; }; extern volatile union CAPCONA_REG *CAPCONAbits; // CAPCONB struct CAPCONB_BITS { unsigned int rsvd1:2; unsigned int CAP6EDGE:2; unsigned int CAP5EDGE:2; unsigned int CAP4EDGE:2; unsigned int CAP6TOADC:1; unsigned int CAP45TSEL:1; unsigned int CAP6TSEL:1; unsigned int rsvd2:1; unsigned int CAP6EN:1; unsigned int CAP45EN:2; unsigned int CAPRES:1; };

// // // // // // // // // // //

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

reserved Edge Detection for Unit 6 Edge Detection for Unit 5 Edge Detection for Unit 4 Unit 6 starts the ADC GP Timer select for Units 4 and 5 GP Timer select for Unit 6 reserved Capture Unit 6 control Capture Unit 4 and 5 control Capture reset

union CAPCONB_REG {

386

unsigned int all; struct CAPCONB_BITS bit; }; extern volatile union CAPCONB_REG *CAPCONBbits; // CAPFIFOA struct CAPFIFOA_BITS { unsigned int rsvd1:8; // 0-7 unsigned int CAP1FIFOSTAT:2; unsigned int CAP2FIFOSTAT:2; unsigned int CAP3FIFOSTAT:2; unsigned int rsvd2:2; };

reserved // 8-9 CAP1 FIFO status // 10-11 CAP2 FIFO status // 12-13 CAP3 FIFO status // 14-15 reserved

union CAPFIFOA_REG { unsigned int all; struct CAPFIFOA_BITS bit; }; extern volatile union CAPFIFOA_REG *CAPFIFOAbits; //CAPFIFOB struct CAPFIFOB_BITS { unsigned int rsvd1:8; //0-7 unsigned int CAP4FIFOSTAT:2; unsigned int CAP5FIFOSTAT:2; unsigned int CAP6FIFOSTAT:2; unsigned int rsvd2:2; };

reserved // 8-9 CAP4 FIFO status // 10-11 CAP5 FIFO status // 12-13 CAP6 FIFO status // 14-15 reserved

union CAPFIFOB_REG { unsigned int all; struct CAPFIFOB_BITS bit; }; extern volatile union CAPFIFOB_REG *CAPFIFOBbits; // ****************** 16-bit extern volatile unsigned int extern volatile unsigned int extern volatile unsigned int

word register *T1CNT; /* *T1CMPR; /* *T1PR; /*

pointers GP timer GP timer GP timer

*********** 1 counter reg */ 1 compare reg */ 1 period */

extern volatile unsigned int *T2CNT; /* GP timer 2 counter reg */ extern volatile unsigned int *T2CMPR; /* GP timer 2 compare reg */ extern volatile unsigned int *T2PR; /* GP timer 2 period reg */ extern volatile unsigned int *CMPR1; /*Compare register for Compare unit1 */ extern volatile unsigned int *CMPR2; /*Compare register for Compare unit2 */ extern volatile unsigned int *CMPR3; /*Compare register for Compare unit3 */ extern volatile unsigned int *T3CNT; /* GP timer 3 counter */ extern volatile unsigned int *T3CMPR; /* GP timer 3 compare */ extern volatile unsigned int *T3PR; /* GP timer 3 period */

387

extern volatile unsigned int *T4CNT; /* GP timer 4 counter reg */ extern volatile unsigned int *T4CMPR; /* GP timer 4 compare reg */ extern volatile unsigned int *T4PR; /* GP timer 4 period reg */ extern volatile unsigned int *CMPR4; /*Compare register for Compare unit4 */ extern volatile unsigned int *CMPR5; /*Compare register for Compare unit5 */ extern volatile unsigned int *CMPR6; /*Compare register for Compare unit6 */ extern extern extern extern extern extern

volatile volatile volatile volatile volatile volatile

unsigned unsigned unsigned unsigned unsigned unsigned

int int int int int int

*CAP1FIFO; *CAP2FIFO; *CAP3FIFO; *CAP1FBOT; *CAP2FBOT; *CAP3FBOT;

/* /* /* /* /* /*

Capture Capture Capture Capture Capture Capture

Channel channel channel channel channel channel

1 2 3 1 2 3

FIFO top */ FIIFO top */ FIFO top */ FIFO BOTtom */ FIFO BOTtom */ FIFO BOTtom */

extern extern extern extern extern extern

volatile volatile volatile volatile volatile volatile

unsigned unsigned unsigned unsigned unsigned unsigned

int int int int int int

*CAP4FIFO; *CAP5FIFO; *CAP6FIFO; *CAP4FBOT; *CAP5FBOT; *CAP6FBOT;

/* /* /* /* /* /*

Capture Capture Capture Capture Capture Capture

Channel channel channel channel channel channel

4 5 6 4 5 6

FIFO top */ FIIFO top */ FIFO top */ FIFO BOTtom */ FIFO BOTtom */ FIFO BOTtom */

#endif

Register Declarations (“DSP24_Ev.c”) #include "DSP24_Ev.h" volatile unsigned int *T1CNT=(void*)0x7401; /* GP timer 1 counter reg */ volatile unsigned int *T1CMPR=(void*)0x7402; /* GP timer 1 compare reg */ volatile unsigned int *T1PR=(void*)0x7403; /* GP timer 1 period */ volatile unsigned int *T2CNT=(void*)0x7405; volatile unsigned int *T2CMPR=(void*)0x7406; volatile unsigned int *T2PR=(void*)0x7407; volatile unsigned int *CMPR1=(void*)0x7417; /*Comp reg for Compare unit1 */ volatile unsigned int *CMPR2=(void*)0x7418; /* Comp reg for Compare unit2 */ volatile unsigned int *CMPR3=(void*)0x7419; /* Comp reg for Compare unit3 */ volatile unsigned int *T3CNT=(void*)0x7501; volatile unsigned int *T3CMPR=(void*)0x7502; volatile unsigned int *T3PR=(void*)0x7503; volatile unsigned int *T4CNT=(void*)0x7505; volatile unsigned int *T4CMPR=(void*)0x7506; volatile unsigned int *T4PR=(void*)0x7507;

/* GP timer 1 counter reg */ /* GP timer 1 compare reg */ /* GP timer 1 period */ /* GP timer 4 counter *. /* GP timer 4 compare */ /* GP timer 4 period */

volatile unsigned int *CMPR4=(void*)0x7517; /* Comp reg for Comp unit4 */

388

volatile unsigned int *CMPR5=(void*)0x7518; /* Comp reg for Comp unit5 */ volatile unsigned int *CMPR6=(void*)0x7517; /* Comp reg for Comp unit6 */ volatile volatile volatile volatile volatile volatile

unsigned unsigned unsigned unsigned unsigned unsigned

int int int int int int

*CAP1FIFO=(void*)0x7423; *CAP2FIFO=(void*)0x7424; *CAP3FIFO=(void*)0x7425; *CAP1FBOT=(void*)0x7427; *CAP2FBOT=(void*)0x7428; *CAP3FBOT=(void*)0x7429;

// CAP1 1 FIFO top // CAP2 2 FIFO top // CAP3 3 FIFO top //CAP1 FIFO bottom // CAP2 FIFO bottom // CAP3 FIFO bottom

volatile volatile volatile volatile volatile volatile

unsigned unsigned unsigned unsigned unsigned unsigned

int int int int int int

*CAP4FIFO=(void*)0x7523; *CAP5FIFO=(void*)0x7524; *CAP6FIFO=(void*)0x7525; *CAP4FBOT=(void*)0x7527; *CAP5FBOT=(void*)0x7528; *CAP6FBOT=(void*)0x7529;

// // // // // //

CAP4 CAP5 CAP6 CAP4 CAP5 CAP6

FIFO FIFO FIFO FIFO FIFO FIFO

top top top bottom bottom bottom

// GPTCONA volatile union GPTCONA_REG *GPTCONAbits=(void*)0x7400; // GPTCONB volatile union GPTCONB_REG *GPTCONBbits=(void*)0x7500; //T1CON volatile union T1CON_REG *T1CONbits=(void*)0x7404; // T2CON volatile union T2CON_REG *T2CONbits=(void*)0x7408; //T3CON volatile union T3CON_REG *T3CONbits=(void*)0x7504; //T4CON volatile union T4CON_REG *T4CONbits=(void*)0x7508; // EVAIMRA volatile union EVAIMRA_REG *EVAIMRAbits=(void*)0x742C; //EVAIMRB volatile union EVAIMRB_REG *EVAIMRBbits=(void*)0x742D; // EVAIMRC volatile union EVAIMRC_REG *EVAIMRCbits=(void*)0x742E; // EVBIMRA volatile union EVBIMRA_REG *EVBIMRAbits=(void*)0x752C; // EVBIMRB volatile union EVBIMRB_REG *EVBIMRBbits=(void*)0x752D; // EVBIMRC volatile union EVBIMRC_REG *EVBIMRCbits=(void*)0x752E; // EVAIFRA volatile union EVAIFRA_REG *EVAIFRAbits=(void*)0x742F; //EVAIFRB

389

volatile union EVAIFRB_REG *EVAIFRBbits=(void*)0x7430; // EVAIFRC volatile union EVAIFRC_REG *EVAIFRCbits=(void*)0x7431; //EVBIFRA volatile union EVBIFRA_REG *EVBIFRAbits=(void*)0x752F; // EVBIFRB volatile union EVBIFRB_REG *EVBIFRBbits=(void*)0x7530; // EVBIFRC volatile union EVBIFRC_REG *EVBIFRCbits=(void*)0x7531; // COMCONA volatile union COMCONA_REG *COMCONAbits=(void*)0x7411; // COMCONB volatile union COMCONB_REG *COMCONBbits=(void*)0x7511; // ACTRA volatile union ACTRA_REG *ACTRAbits=(void*)0x7413; // ACTRB volatile union ACTRB_REG *ACTRBbits=(void*)0x7513; // DBTCONA volatile union DBTCONA_REG *DBTCONAbits=(void*)0x7415; // DBTCONB volatile union DBTCONB_REG *DBTCONBbits=(void*)0x7515; // CAPFIFOA volatile union CAPFIFOA_REG *CAPFIFOAbits=(void*)0x7422; //CAPFIFOB volatile union CAPFIFOB_REG *CAPFIFOBbits=(void*)0x7522; // CAPCONA volatile union CAPCONA_REG *CAPCONAbits=(void*)0x7420; // CAPCONB volatile union CAPCONB_REG *CAPCONBbits=(void*)0x7520;

390

391

APPENDIX

B

Header and C files for the LF2407A ISRs

Declarations of Interrupt Service Routines (“DSP24_DefaultISR.h”) #ifndef DSP24_DEFAULT_ISR_H #define DSP24_DEFAULT_ISR_H #include "DSP24_Core.h" // ***************LEVEL 1 interrupts ********************** // **********(including CPU INT1 interrupt)**************** interrupt void INT1_GISR(void); // GISR for all INT1 level peripherals // Now here comes all Specific ISRs... // 1) Power drvive Protection Interrupt pin A interrupt void PDPINTA_ISR(void); // 2) Power Drive Protection interrupt pin B interrupt void PDPINTB_ISR(void); // 3) ADC interrupt in high priority interrupt void ADCINT_HP_ISR(void); // 4) al pin1 interrupt in high priority interrupt void XINT1_HP_ISR(void); // 5) al pin2 interrupt in high priority interrupt void XINT2_HP_ISR(void); // 6) SPI interrupt in high priority interrupt void SPIINT_HP_ISR(void); // 7) SCI Receive (Rx) interrupt in high priority interrupt void RXINT_HP_ISR(void); // 8) SCI Transmit (Tx) interrupt in high priority interrupt void TXINT_HP_ISR(void); // 9) CAN mailbox interrupt in high priority interrupt void CANMBINT_HP_ISR(void); // 10) CAN Error interrupt in high priority interrupt void CANERINT_HP_ISR(void); // ***************LEVEL 2 interrupts ********************** // **********(including CPU INT2 interrupt)**************** interrupt void INT2_GISR(void);// GISR for all INT2 level peripherals // Now here comes all specific ISRs... // 1) Compare 1 interrupt void // 2) Compare 2 interrupt void // 3) Compare 3 interrupt void

Interrupt CMP1INT_ISR(void); interrupt CMP2INT_ISR(void); Interrupt CMP3INT_ISR(void);

392

// 4) Timer 1 Period interrupt interrupt void T1PINT_ISR(void); // 5) Timer 1 Compare interrupt interrupt void T1CINT_ISR(void); // 6) Timer 1 Underflow interrupt interrupt void T1UFINT_ISR(void); // 7) Timer 1 Overflow interrupt interrupt void T1OFINT_ISR(void); // 8) Compare 4 interrupt interrupt void CMP4INT_ISR(void); // 9) Compare 5 interrupt interrupt void CMP5INT_ISR(void); // 10) Compare 6 interrupt interrupt void CMP6INT_ISR(void); // 11) Timer 3 Period interrupt interrupt void T3PINT_ISR(void); // 12) Timer 3 Compare interrupt interrupt void T3CINT_ISR(void); // 13) Timer 3 Underflow interrupt interrupt void T3UFINT_ISR(void); // 14) Timer 3 Overflow interrupt interrupt void T3OFINT_ISR(void); // ***************LEVEL 3 interrupts ********************** // **********(including CPU INT3 interrupt)**************** interrupt void INT3_GISR(void);// GISR for all INT3 level peripherals // Now here comes all specific ISRs... //1) Timer interrupt //2) Timer interrupt //3) Timer interrupt //4) Timer interrupt //5) Timer interrupt //6) Timer interrupt //7) Timer interrupt //8) Timer interrupt

2 Period interrupt void T2PINT_ISR(void); 2 Compare interrupt void T2CINT_ISR(void); 2 Underflow interrupt void T2UFINT_ISR(void); 2 Overflow interrupt void T2OFINT_ISR(void); 4 Period interrupt void T4PINT_ISR(void); 4 Compare interrupt void T4CINT_ISR(void); 4 Underflow interrupt void T4UFINT_ISR(void); 4 Overflow interruopt void T4OFINT_ISR(void);

// ***************LEVEL 4 interrupts ********************** // **********(including CPU INT4 interrupt)**************** interrupt void INT4_GISR(void);// GISR for all INT4 level peripherals // Now here comes all specific ISRs... //1) Capture 1 interrupt interrupt void CAP1INT_ISR(void); //2) Capture 2 interrupt interrupt void CAP2INT_ISR(void); //3) Capture 3 interrupt interrupt void CAP3INT_ISR(void); //4) Capture 2 interrupt

393

interrupt void CAP4INT_ISR(void); //5) Capture 5 interrupt interrupt void CAP5INT_ISR(void); //6) Capture 6 interrupt interrupt void CAP6INT_ISR(void); // ***************LEVEL 5 interrupts ********************** // **********(including CPU INT5 interrupt)**************** interrupt void INT5_GISR(void);// GISR for all INT5 level peripherals // Now here comes all specific ISRs... //1) SPI interrupt in low priority interrupt void SPIINT_LP_ISR(void); //2) SCI receive (RX) interrupt in low priority interrupt void RXINT_LP_ISR(void); //3) SCI Transmit (Tx) interrupt in low priority interrupt void TXINT_LP_ISR(void); //4) CAN mailbox interrupt in low priority interrupt void CANMBINT_LP_ISR(void); //5) CAN Error interrupt in low priority interrupt void CANERINT_LP_ISR(void); // ***************LEVEL 6 interrupts ********************** // **********(including CPU INT6 interrupt)**************** interrupt void INT6_GISR(void);// GISR for all INT6 level peripherals // Now here comes all specific ISRs... //1) ADC interrupt in low priority interrupt void ADC_LP_ISR(void); //2) al pin1 interrupt in low priority interrupt void XINT1_LP_ISR(void); //3) al pin 2 interrupt in low priority interrupt void XINT2_LP_ISR(void); #endif

// end

Implementations of Interrupt Service Routines (“DSP24_DefaultISR.c”) #include "DSP24_DefaultIsr.h" // ************** INT 1 Specific ISRs **************** // 1) Power drvive Protection Interrupt pin A interrupt void PDPINTA_ISR(void) { } // 2) Power Drive Protection interrupt pin B interrupt void PDPINTB_ISR(void) { } // 3) ADC interrupt in high priority interrupt void ADCINT_HP_ISR(void) {

394

} // 4) External pin1 interrupt in high priority interrupt void XINT1_HP_ISR(void) { } // 5) External pin2 interrupt in high priority interrupt void XINT2_HP_ISR(void) { } // 6) SPI interrupt in high priority interrupt void SPIINT_HP_ISR(void) { } // 7) SCI Receive (Rx) interrupt in high priority interrupt void RXINT_HP_ISR(void) { } // 8) SCI Transmit (Tx) interrupt in high priority interrupt void TXINT_HP_ISR(void) { } // 9) CAN mailbox interrupt in high priority interrupt void CANMBINT_HP_ISR(void) { } // 10) CAN Error interrupt in high priority interrupt void CANERINT_HP_ISR(void) { } // ************** INT 2 Specific ISRs **************** // 1) Compare 1 Interrupt interrupt void CMP1INT_ISR(void) { } // 2) Compare 2 interrupt interrupt void CMP2INT_ISR(void) { } // 3) Compare 3 Interrupt interrupt void CMP3INT_ISR(void) { } // 4) Timer 1 Period interrupt interrupt void T1PINT_ISR(void) { } // 5) Timer 1 Compare interrupt interrupt void T1CINT_ISR(void) { } // 6) Timer 1 Underflow interrupt interrupt void T1UFINT_ISR(void) { } // 7) Timer 1 Overflow interrupt

395

interrupt void T1OFINT_ISR(void) { } // 8) Compare 4 interrupt interrupt void CMP4INT_ISR(void) { } // 9) Compare 5 interrupt interrupt void CMP5INT_ISR(void) { } // 10) Compare 6 interrupt interrupt void CMP6INT_ISR(void) { } // 11) Timer 3 Period interrupt interrupt void T3PINT_ISR(void) { } // 12) Timer 3 Compare interrupt interrupt void T3CINT_ISR(void) { } // 13) Timer 3 Underflow interrupt interrupt void T3UFINT_ISR(void) { } // 14) Timer 3 Overflow interrupt interrupt void T3OFINT_ISR(void) { } // ************** INT 3 Specific ISRs **************** //1) Timer 2 Period interrupt interrupt void T2PINT_ISR(void) { } //2) Timer 2 Compare interrupt interrupt void T2CINT_ISR(void) { } //3) Timer 2 Underflow interrupt interrupt void T2UFINT_ISR(void) { } //4) Timer 2 Overflow interrupt interrupt void T2OFINT_ISR(void) { } //5) Timer 4 Period interrupt interrupt void T4PINT_ISR(void) { } //6) Timer 4 Compare interrupt interrupt void T4CINT_ISR(void) { }

396

//7) Timer 4 Underflow interrupt interrupt void T4UFINT_ISR(void) { } //8) Timer 4 Overflow interruopt interrupt void T4OFINT_ISR(void) { } // ************** INT 4 Specific ISRs **************** //1) Capture 1 interrupt interrupt void CAP1INT_ISR(void) { } //2) Capture 2 interrupt interrupt void CAP2INT_ISR(void) { } //3) Capture 3 interrupt interrupt void CAP3INT_ISR(void) { } //4) Capture 2 interrupt interrupt void CAP4INT_ISR(void) { } //5) Capture 5 interrupt interrupt void CAP5INT_ISR(void) { } //6) Capture 6 interrupt interrupt void CAP6INT_ISR(void) { } // ************** INT 5 Specific ISRs **************** //1) SPI interrupt in low priority interrupt void SPIINT_LP_ISR(void) { } //2) SCI receive (RX) interrupt in low priority interrupt void RXINT_LP_ISR(void) { } //3) SCI Transmit (Tx) interrupt in low priority interrupt void TXINT_LP_ISR(void) { } //4) CAN mailbox interrupt in low priority interrupt void CANMBINT_LP_ISR(void) { } //5) CAN Error interrupt in low priority interrupt void CANERINT_LP_ISR(void) { }

397

// ************** INT 6 Specific ISRs **************** //1) ADC interrupt in low priority interrupt void ADCINT_LP_ISR(void) { } //2) External pin1 interrupt in low priority interrupt void XINT1_LP_ISR(void) { } //3) External pin 2 interrupt in low priority interrupt void XINT2_LP_ISR(void) { } // ***************INT1 GISR ********************** interrupt void INT1_GISR(void) { // GISR for all INT1 level peripherals unsigned int peripheral; asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM"); if (peripheral==0x0020) { //PDPINTA PDPINTA_ISR(); } else if (peripheral==0x0019) { // PDPINTB PDPINTB_ISR(); } else if (peripheral==0x0004) { //ADC high priority ADCINT_HP_ISR(); } else if (peripheral==0x0001) { //XINT1 high priority XINT1_HP_ISR(); } else if (peripheral==0x0011) { // XINT2 high priority XINT2_HP_ISR(); } else if (peripheral==0x0005) { // SPIINT h.p. SPIINT_HP_ISR(); } else if (peripheral==0x0006) { // SCI RX hp RXINT_HP_ISR(); } else if (peripheral==0x0007) { // SCI TX hp TXINT_HP_ISR(); } else if (peripheral==0x0040) { // CAN MB hp CANMBINT_HP_ISR(); } else if (peripheral==0x0041) { // CAN error hp CANERINT_HP_ISR(); } // now resetting the core INT1 flag IFR |= 1; } // ***************INT 2 GISR********************** interrupt void INT2_GISR(void) {// GISR for all INT2 level peripherals unsigned int peripheral; asm(" SETC INTM"); peripheral=*PIVR;

398

asm(" CLRC INTM"); if (peripheral==0x0021) { //CMP1 CMP1INT_ISR(); } else if (peripheral==0x0022) { //CMP2 CMP2INT_ISR(); } else if (peripheral==0x0023) { // CMP3 CMP3INT_ISR(); } else if (peripheral==0x0027) { // Timer 1 period T1PINT_ISR(); } else if (peripheral==0x0028) { // Timer 1 Compare T1CINT_ISR(); } else if (peripheral==0x0029) { // Timer 1 UnderFlow T1UFINT_ISR(); } else if (peripheral==0x002A) { // Timer OverFlow T1OFINT_ISR(); } else if (peripheral==0x0024) { // CMP 4 CMP4INT_ISR(); } else if (peripheral==0x0025) { // CMP5 CMP5INT_ISR(); } else if (peripheral==0x0026) { // CMP6 CMP6INT_ISR(); } else if (peripheral==0x002F) { // Timer 3 Period T3PINT_ISR(); } else if (peripheral==0x0030) { //Timer 3 Compare T3CINT_ISR(); } else if (peripheral==0x0031) { //Timer 3 underflow T3UFINT_ISR(); } else if (peripheral==0x0032) {// Timer 3 Overflow T3OFINT_ISR(); } // reset the core INT2 flag IFR |= 2; } // *************** INT3 GISR ********************** interrupt void INT3_GISR(void) {// GISR for all INT3 level peripherals unsigned int peripheral; asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM"); if (peripheral==0x002B) { // Timer 2 period T2PINT_ISR(); } else if (peripheral==0x002C) { //Timer 2 Compare T2CINT_ISR(); } else if (peripheral==0x002D) { // Timer 2 underflow T2UFINT_ISR(); } else if (peripheral==0x002E) { // Timer 2 overflow T2OFINT_ISR(); } else if (peripheral==0x0039) { // Timer 4 period T4PINT_ISR();

399

} else if (peripheral==0x003A) { // Timer 4 compare T3CINT_ISR(); } else if (peripheral==0x003B) { // Timer 4 underflow T4UFINT_ISR(); } else if (peripheral==0x003C) { //timer 4 overflow T4OFINT_ISR(); } // reset the core INT3 flag IFR |= 4; } // *************** INT4 GISR ********************** interrupt void INT4_GISR(void) {// GISR for all INT4 level peripherals unsigned int peripheral; asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM"); if (peripheral==0x0033) { // capture 1 CAP1INT_ISR(); } else if (peripheral==0x0034) { // capture 2 CAP2INT_ISR(); } else if (peripheral==0x0035) { // capture 3 CAP3INT_ISR(); } else if (peripheral==0x0036) { //capture 4 CAP4INT_ISR(); } else if (peripheral==0x0037) { // capture 5 CAP5INT_ISR(); } else if (peripheral==0x0038) { // capture 6 CAP6INT_ISR(); } // reset INT4 interrupt flag IFR |= 8; }

// ***************INT5 GISR ********************** interrupt void INT5_GISR(void) {// GISR for all INT5 level peripherals unsigned int peripheral; asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM"); if (peripheral==0x0005) { //SPI lp SPIINT_LP_ISR(); } else if (peripheral==0x0006) { //SCI RX lp RXINT_LP_ISR(); } else if (peripheral==0x0007) { // SCI TX lp TXINT_LP_ISR(); } else if (peripheral==0x0040) { // CAN mailbox lp

400

CANMBINT_LP_ISR(); } else if (peripheral==0x0041) { // CAN Error lp CANERINT_LP_ISR(); } // reset INT 5 flag IFR |= 0x10; } // *************** INT6 GISR ********************** interrupt void INT6_GISR(void) {// GISR for all INT6 level peripherals unsigned int peripheral; asm(" SETC INTM"); peripheral=*PIVR; asm(" CLRC INTM"); if (peripheral==0x0004) { //ADC lp ADCINT_LP_ISR(); } else if (peripheral==0x0001) { // XINT1 lp XINT1_LP_ISR(); } else if (peripheral==0x0011) { // XINT2 lp XINT2_LP_ISR(); } // reset INT6 flag IFR |= 0x20; }

401

402

Suggest Documents