Programming The DRAGON12-PLUS-USB in C and Assembly Language Using CodeWarrior
Programming The DRAGON12-PLUS-USB in C and Assembly Language Using CodeWarrior
DRAGON‘12-Plus-USB™
in C and Assembly Language
Using CodeWarrior™
Richard E. Haskell
Darrin M. Hanna
LBE Books
Rochester Hills, MI
Copyright 2011, 2018 by LBE Books. All rights reserved.
ISBN 978-0-9824970-2-9
Second Printing
www.lbebooks.com
Preface
Microcontrollers such as the Freescale MC9SDG256 are remarkable devices. They contain
not only a sophisticated microprocessor with a rich set of instructions and addressing modes, but also
contain built-in RAM, EEPROM, and flash memoryas well as numerous useful I/O ports, including
parallel 1/O, several different types of serial I/O, timers, and A/D converters.
Wewill use a particular microcontroller, the Freescale MC9S 12DG256,thatit is one of the
more powerful in the popular HCS12 family of microcontrollers from Freescale with lots of /O
capabilities. This microcontroller is available on the DRAGON12-Plus-USB™development board
from Wytec, Inc. The DRAGON12-Plus-USB™has many built-in I/O devices including LEDs,
switches, four 7-segment displays, a hex keypad, an LCD display, a D/A converter chip, an on-board
speaker, a built-in H-bridge for driving motors, convenient headers for connecting servos and an
accelcrometer board available from Wytec, and female headers for connecting to your circuits on the
built-in protoboard.
A previous book from LBE Books, Learning By Example Using C — Programming the
DRAGON12-Plus™Using CodeWarrior showed howto write programs in C with a minimum of
effort for this development board. We did this by providing you with a CodeWamiorstationery
project that contained an assembly language file to do all the low-level interaction with the /O
registers. These assembly language routines becomefunction calls for your C program. Thus, in this
previous book you didn't have to learn any assembly language to get sophisticated programs to work
on this development board.
This current book provides these same CodeWamniorStationeryprojects so that you can wnite
C programs easily for the DRAGON 12-Plus-USB™development board. However, in this book we
look under the hood to see howall of the assembly language routines that are included in the
stationery project work. This way you will learn how to program the HCS12 microcontroller in
assembly language and howto call these assembly language routines from your top-level C program.
You will therefore be able to write your own assembly language subroutines that you can call from
your C program, and in this way get the maximum performance from the MC9SDG256
microcontroller.
In Chapter | we introduce the DRAGON 12-Plus-USB™development board. Chapter 2 will
include examples of using the parallel ports for outputs and Chapter 3 will include examples of using
the parallel ports for inputs. Liquid crystal displays are described in Chapter 4. The important topic
of intermupts are introduced in Chapter 5. Examples using the two 8-channel A/D converters that are
built into the MC9S12DG256 microcontroller are given in Chapter 6. Examples that show how to
use pulse-width modulation (PWM) to control the speed of a DC motoror the position of a servo are
presented in Chapter 7. Chapter 8 includes examples of using the serial communication interface
(SCI) and Chapter 9 shows howto use the serial peripheral interface (SPI). Examples using the
built-in timer module are given in Chapter 10 and an example of using the MC9S12DG256
microcontroller for fuzzy control is included in Chapter 11.
Manycolleagues, students and reviewers have influenced the development of this book.
Their stimulating discussions, probing questions, and critical comments are greatly appreciated.
Special thanks go to Michael Latcha and Osamah Rawashdeh with whom we have had many useful
discussionsrelated to the contents of this book.
Richard E. Haskell
Darrin M. Hanna
iil
Programming the DRAGON‘12-Plus-USB
in C and Assembly Language
Using CodeWarrior™
Table of Contents
1. Introduction l
1.1 From Microprocessors to Microcontrollers |
1.2 DRAGON12-Plus-USB™ Board 4
1.3 The CodeWarrior Development Tools 5
y
5. Interrupts 66
5.1 Hardware Interrupts 66
5.2 Real-Time Interrupts 67
Example 14 — Blinking 7-Segment Display 68
Example 15 — Interrupt-Driven Traffic Light 73
Example 16 — Interrupt-Driven Blinking SOS 76
6. Analog-to-Digital Converter 78
6.1 Analog-to-Digital Conversion 78
6.2 Using the MC9S12DG256 A/D Converters 80
Example 17 — Reading the Potentiometer Valuc 81
6.3. Measuring Acceleration 85
Example 18 — Measuring the x-)-z Components of Acceleration 85
Example 19 — Measuring the Coefficient of Static Friction 86
6.4 Measuring Temperature 87
Example 20 — Displaying the Temperature on the LCD 88
vi
10. Timer 132
10.1 Output Compare 132
Example 29 — Interrupt-Driven Pulse Train 133
Example 30 — Playing Musical Notes with the Keypad 139
10.3. Input Capture 143
Example 31 — Measuring Input Pulse Widths 144
Index 209
Vii
Introduction 1
Chapter1
Introduction
Thus, the first microprocessors consisted only of a CPU that could address external memory
as shown in Fig. 1.1.
Data
Bus
RAM
CPU
ROM
(registers)
vo
Address
Bus
Figure 1.1 A microprocessor (CPU) connected to external memory
The external memory shown in Fig. 1.1 consists of read-write memory (RAM), read-
only memory (ROM), and input/output memory (I/O). Typically the I/O memory consists of
dedicated special-purpose devices for performing such operations asparallel 1/O, serial I/O,
timer functions, and analog-to-digital (A/D) conversion. These I/O devices contain registers
that look like memory locations to the CPU. The RAM in Fig. 1.1 could be cither static
RAM (SRAM) or dynamic RAM (DRAM). Dynamic RAM can contain more bytes of
memory than static RAM for the samesize chip, but requires additional circuitry to refresh
the data pcriodically to keep it from being lost. Other types of memory devices that might be
connected to the address and data busses in Fig. 1.1 include erasable programmable read-
only memory (EPROM),electrically-crasable programmable read-only memory (EEPROM),
and flash EEPROM. Both flash EEPROM and EEPROMarc non-volatile memory that will
maintain their data when poweris removed. Individual bytes can be crased and programmed
in EEPROM while flash EEPROMsnormally require crasing the entire memory array at one
time.
As integrated circuit technology developed over the years one of the trends has been
the development of faster and more complex microprocessors such as the Intcl 80x86 and
Pentium and the Motorola 680x0 and PowerPC. These microprocessors are in many of the
popular desktop computers used in offices all over the world. Another trend has been to
package more and morcfunctionality onto a single chip. The Motorola 6801 was introduced
in 1978 and included a small amount of RAM and ROMas well as parallel and scrial I/O on
a single chip. The following year Motorola introduced an EPROMversion ofthe 6801, the
68701, as well as the first of the low-cost 6805 family of microcontrollers.
The first 68HC11 was introduced by Motorola in 1985. This 8-bit microcontroller
(the A8 part) contained on a single chip the CPUI1 microprocessor, 8 Kbytes of ROM, 256
bytes of RAM, 512 bytes of EEPROM,up to 38 parallel I/O lines, a 16-bit timer that
included 3 input captures and 5 output compares, a synchronous scrial peripheral interface
(SPI), an asynchronous scrial communications interface (SCI), and an 8-channel, 8-bit A/D
converter. Since 1985 over five dozen different 68HC11 parts have been introduced by
Motorola. These parts differ in the types and amounts of on-board resources that are
includedin the chip.
Introduction 3
HCS12
Microcontroller ND
PARALLEL
vO SPI} SCI
The DRAGON 12-Plus-USB board from Wytec is shown in Fig. 1.3. This board
contains the Freescale MC9SI2DG256 microcontroller surrounded by four convenient
female headers that bring out all of the I/O ports. This makesit easy to interface to your
own I/Ocircuitry on the attached protoboard. In addition the board contains a four-digit 7-
segment display, four pushbutton switches, an 8-position DIP switch, eight LEDs, a
potentiometer for reading in an
analog voltage between 0 and 5
volts, a liquid crystal display
(LCD),a4x4 keypad, a D/A
converter chip, a temperature
sensor, a light sensor, and an IR
transmitter and receiver that :
can be used to detect the eee BSS.
presence of an object. The ee LLL
DRAGON 12-Plus-USB also
=~ es
has an on-board speaker, a
built-in H-bridge for driving
motors, and convenient headers
for connecting servos and an
accelerometer board available Figure 1.3 The Wytec DRAGON12-Plus-USB board
from Wytec.
the tutorial in Appendix A, these assembly language routines will automatically be included
in the file main.asm that will be part of your project. The top-level design in your project
will be a C program that is stored in the file main.c. In this book, we will show you how to
write your own assembly language routines that are stored in main.asm and howto call these
routines as C function calls from your top-level C program stored in main.c.
To use CodeWarrior with the DRAGON12-Plus-USB, the MC9S12DG256
microcontroller on these boards must contain the Serial Monitor. This Serial Monitor is 2
kbytes of code stored at addresses $F800 — SFFFF in the flash memory. This code is
executed when you press the reset button on the board and allows CodeWarrior to
communicate with your board through the serial port. When you order one of the
development boards from Wytec, make sure to specify that you want the Serial Monitor
installed; otherwise, it will come with the Dbug monitor that is not compatible with
CodeWarrior.
Getting the DRAGON 12-Plus-USB board to do whatever you wantis challenging
and lots of fun. Therefore, let’s get started!
6 Chapter2
Chapter 2
In this chapter you will learn how parallel ports are used to turn on LEDsand the
segments of a 7-segment display. Asin all of our examples wewill first show you how to do
it entirely in C, and then we will show howto doit using C calls to our built-in assembly
language routines. You will also learn how these assembly language routines work. Using
these built-in assembly language routines will make your C programs much shorter and
easier to write.
Mostofthe I/O ports listed in Table 2.1 have alternate or special optional functions
many of which we will consider in later examples. Whenthe pins of an I/O port are not
being used for oneofthese alternate functions they can beused as general purpose I/O pins.
There are several different operating modes for the MC9S12DG256 including a
single-chip mode and expanded external memory modes. In the expanded external memory
modes, ports A and B are used for multiplexed address and data busses. The DRAGON 12-
Plus-USB board operates in the single-chip mode, so that ports A and B are available for
parallel I/O. Port A is connected to the 4 x 4 keypad. On the DRAGONI12-Plus-USB board
Parallel Ports - Outputs 7
Port B is connected to the red LEDsandto the segments of the 7-segment displays. You will
also use Port B to control the direction of rotation of a motor using the H-bridge.
When using CodeWarrior the Port Names and DDR Names in Table 2.1 are
associated with the specific register addresses given in Table 2.2. Wewill see in Examples
3 and 4 how you can use the CodeWarrior debugger to observe the contents ofthese register
addresses change as you execute a program.
the output of Port B. To display different digits on the four 7-segment displays, the displays
must be multiplexed in time as will be shown in Example 6.
In this example, we will show how writing to ports can tum on the LEDs and 7-
segmentdisplays on the DRAGON| 2-Plus-USB.
Followthe steps in the tutorial in Appendix A to set up CodeWamor andcreate a
newproject called Example! where you select the stationery file LBE_ DRAGONI2-Plus-
USB. When youopenthefile main.cin the Sourcefolder you shouldsee the program shown
in Listing 2.1. All text following a double slash // will be a commenttothe end¢of the line
The seven statements following the comment /* put your own code bere *
will do the following:
1. The statement PLL_init( ) will set the system clock frequency to 24 MHz as
described above. This should be thefirst statement in all of your programs.
2. The statement DDRB = OxFF will set all bits in data direction register B to | and
thereforeto all outputs. The notation 0.x means that FFts a hexadecimal number
equal to the binary number ILITTIIIT.
3. The statement DDRJ = OxFF will set all bits in data direction register J to | and
therefore to all outputs.
4. The statement DDRP = OxFF will set all bits in data direction register P to IT and
therefore to all outputs.
5. The statement P7/ = 0x00 will set all bits of port J to zero andtherefore will end!
the LEDs.
6. The statement PTP = 0x00 will set all bits of port P to zero and therefore will enadle
the 7-segmentdisplays.
7. The statement PORTB = 0x55 will set the bits in port B to OLOLOLOL and therefore
turn on segmentsa, c, ¢, and g shown in Fig. 2.3. (Recall thata [turn on a segment
on the 7-segmentdisplay of the DRAGON 1 2-Plus-USB.) [he bits in pert 3 are
connected to the segments ofthe 7-segment display as shown in big. 24.
The statement for(;;) {} will just loop on itself forever. We wall descmbe the
use of the C for loop in more detail in Example 3
Port B Register
7 6
“Pas l ren Pag ra za | PEO) PORTA
dp g a segment
void main(void) {
/* put your own code here */
PLLinit(); // set system clock frequency to 24 MHz
DDRB = Oxff; // Port B is output
DDRJ = Oxff; // Port J is output
DDRP = Oxff; // Port P is output
PTJ = 0x00; // enable LED
PTP = 0x00; // enable all 7-segment displays
// turn on every other led and segment on 7-seg displays
PORTB = 0x55;
Run the program shownin Listing 2.1. Then re-run the program with the value of
PTJ changed to 0x02. This should disable the LEDs. Next change P7J back to 0x00 and
change the value of P7P to Ox0A. This should disable digits 1 and 3 where digits are
labeled 0 — 3 from left to right.
LEDs
Listing 2.2 will turn on every other LED while disabling the 7-segment displays. The
function seg7_disable() will disable the four 7-segmentdisplays by setting the lower4 bits of
Port P to 1 (see Fig. 2.3). The function /ed_enable() will enable the LEDsbysetting the data
direction registers for Port B and Port J to outputs, clearing DJ// to 0, and turning offall
LEDsbyclearing all bits in Port B to zero. Finally, the function /eds_on(0x55) will write
the hex value 0x55 to the Port B data register, thus turning on every other LED starting at the
right.
void main(void) {
PLLinit (); // set system clock frequency to 24 MHz
seg/7_disable(); // disable 7-segment displays
led_enable(); // enable leds
leds_on(0x55); // turn on every other led
All of the C function calls used in this book are defined as assembly language
routines in the file main.asm, located in the source folder of your CodeWarrior project. You
should refer to Appendix B for a discussion of assembly language programming. The
assembly languageroutines for the first six C function calls shown in Table 2.3 are shownin
Listing 2.3. Note that the namesof the C function calls must be the names ofthe labels for
the corresponding assembly language routine. These labels end with a colon. The
statements
addressesofall of these register names are defined using equate statements (equ) in the file
mc9s12dg256.inc, which is located in the /ibraries folder in your CodeWarrior project.
Examples of this equate statement are shownin Listing 2.4.
In the subroutine /ed_enable in Listing 2.3, the statement bclr pPTJ,$02 will clear
bit 1 of the Port J data register. This bit clear statement will AND the complementof the
mask $02 with PTJ. Since the complement of $02 = 00000010 is 11111101, this mask will
clear bit | of PTJ, which enables all LEDs(see Fig. 2.2).
The next statement in the subroutine /ed_enable in Listing 2.8 is clr PORTB, which
clears all bits of Port B to zero, and thus turns off all LEDs.
Listing 2.3 led and seg7 Assembly Language Routines from main.asm
: LEDS
led_enable:
movbd #SFF,DDRB ; DataDirB -->all outputs
movb #SFF,DDRJ ; DataDirJd -->all outputs
belr PpTJ, $02 ; enable leds PJ1 = 0
elr PORTB ; Turn-off all LEDS
rts
leds_on:
stab PORTB ; turn on selected led
rts
leds off:
clr PORTB ; turn off all leds
rcs
led_disable:
movb #SFF,DDRJ ; DataDirJ -->all outputs
bset PTJ,$02 >; enable leds PJl1 = 0
rts
; 7-Segment Displays
seg7 enable:
movb #SFF,DDRB ; DataDirB -->all outputs
movb #SFF,DDRP ; DataDirP -->all outputs
belr PTP,SOF ; enable 7-seg digits PTP[0:3] = 0000
elr PORTB ; Turn-off all 7-seg digits
rts
seg/7 disable:
movb #SFF,DDRP ; DataDirP -->all outputs
bset PTP,SOF ; disable 7-seg digits PTP[0:3] = 1111
rts
The C function call /ed_enable( ) in Listing 2.2 gets compiled as a jump to subroutine
(JSR) assembly language instruction that jumps to the subroutine ledenable: in Listing
2.8. The last statement in this subroutine is a return from subroutine (RTS) instruction,
which will return to the statement following the JSR /ed_enable statement. This will be the
C function call /eds_on(0x55) in Listing 2.2, which will compile to a JSR to the subroutine
Parallel Ports - Outputs 13
leds_on in Listing 2.2. Thefirst instruction in this subroutine is stab PORTB. The reason
for this instruction is because the C function call /eds_on(int) in Table 2.3 passes a 16-bit
integer to the assembly language subroutine. If there is only one 16-bit integer passed to the
subroutine, it is passed in accumulator D, the concatenation of accumulators A and B. Thus,
the 8-bit byte 0x55, which is passed to the subroutine /eds_on in Listing 2.2, will be in
accumulator B. This value then gets stored in the Port B data register, PORTB, whichwill
turn on every other LED starting at the right. The RTSinstruction will return to the for loop
in Listing 2.2, which gets compiled to an assembly language statement that branches on
itself.
The C function call seg7_disable( ) in Listing 2.2 gets compiled as a jump to the
subroutine seg7disable in Listing 2.3. The first statement in this subroutine is movh
#$FF,DDRP, which will set the data direction register of Port P to all ones. The next
statement is the bit set statement bset PTP,SOF, which will OR the mask SOF with P7P and
thus set the lower 4 bits of the Port P data register to one. This will disable all four common-
cathode 7-segmentdisplays (see Fig. 2.3).
Follow Part 2 of the CodeWarrior tutorial in Appendix A to see howto single step
through these assembly languageinstructions and watch exactly what is going on.
Onelast step is required to implement C functioncalls as assembly language routines.
You must include a declaration of the function in the file mainasm, The declarations for
the eight functions in Table 2.3 are showninListing 2.5.
7-Segment Displays
Listing 2.6 will turn on every other segmentof the 7-segment display number 2 while
disabling the LEDs. The digits are numbered to 3 from left to right. Thus, digit 2 is the
third digit fromthe left. Referring to Table 2.3, the function /ed_disable( ) will disable the
eight red LEDs and the function seg7_enable( ) will enable the four 7-segmentdisplays. The
function seg7_on(0x55,2) will turn on segments a,c, e, and g ofdigit 2.
The assembly language routines for the four seg7 C function calls shown in Table 2.3
are shownin Listing 2.7. The statement
defines eight bytes that contain the constant hex values $01, $02, $04, $08, $10, $20, $40,
and $80. Thus, each byte is a mask with only one ofthe eight bits set to 1. The label MASK
is the address of the byte containing $01. Thus, MASKis a table that we can index into (with
an index value of 0 — 7) to select one of the eight byte values.
void main(void) {
PLLanit(); // set system clock frequency to 24 MHz
leddisable(); // disable leds
seg7 enable(); // enable 7-segment displays
seg7_on(0x55,2); // turn on every other segment on digit 2
.
4
7-Segment Displays
seg7 enable:
movb #SFF,DDRB ; DataDirB -->all outputs
movb #SFF,DDRP ; DataDirP -->all outputs
bclr PTP,S$OF ; enable 7-seg digits PTP[0:3} = 0000
clr PORTB ; Turn-off all 7-seg digits
rts
seg7 disable:
movb #SFF,DDRP : DataDirP -->all outputs
bset PTP,SOF ; disable 7-seg digits PTP[0:3] = illil
rts
.
, display selected segments on one digit
; void seg7 on(int segs, int digit#);
; digit# is in D
; segs is at 2,sp (hex value to store in Port B)
seg7on:
ldy #MASK
aby
ldaa O,y ;A = mask
coma
staa PTP ;enable digit digit#
ldd 2,sp 7B = segs
stab PORTB
rts
seg7soff:.
clr PORTB ; turn off all 7-segment displays
rts
SP —t Ret AddrH
Ret Addr L
SP+2 ——> 00
55
The first instruction in the subroutine seg7_on in Listing 2.7 is ldy #MASK. This will
load the address of the table MASK into register Y. The next instruction, aby, will add the
value in accumulator B (whichis 2, being passed in accumulator D) to the value in Y, leaving
16 Chapter 2
the sum in Y. Thus, Y nowpoints to the third byte in the MASK table, which contains the
constant $04. The next instruction, ldaa 0,y, will load this value $04 into accumulatorA,
and the nextinstruction, coma, will complementall bits of A, leaving the eight bits 11111011
in accumulator A. This value is then stored in the Port P data register, PTP, which will
enable only digit 2 of the four 7-segment displays. To light the segments of this digit, we
just need to store the hex value $55 from Fig. 2.5 in PORTB. The statement 1dd 2,sp will
load accumulator D with the 16-bit value located at SP +2 as shownin Fig. 2.5. The byte
$55 will be in accumulator B, so nowthe instruction stab PORTB will store this value in the
fort B data register, which will turn on segmentsa, c, e, and g of the 7-segment display on
igit 2.
Wewill generate a delay by making a simple software delay loop. A more accurate
way of producing a delay1s to use the timer module in the MC9S12DG256. Wewill look at
how to do this in Chapters 4 and 9. Listing 2.8 shows how to makea software delay using
two nested for loops in the function delay( ). The program in Listing 2.8 will blink on and
off the seven right-most red LEDsplusall segments of the right-most 7-segmentdisplay.
The C forloop has the following general form
In the inner for loop in the delay( ) function in Listing 2.8 the initial_index is defined by the
Statement j =0, where/ is a 16-bit integer declared along with in the statementinti, /;.
The terminal_index in the innerfor loop is defined by the statement j < 5999;and the
incrementis defined by the statement 7 ++. The statement j++ is equivalentto j= /j+1
which just increments j by 1. Thus, in this for loop the index / starts at 0, the statements
between the braces {...} are executed (there are no statements in the innerfor loop in Listing
2.8), the index / is incremented by 1, and the statements between the braces are executed
again. This process continues until the terminal_index is reached,or in this case when the
statement j < 5999; is false, i.e. when j gets incremented to 5999. Thus, this for loop will
execute 5999 times. We chose this number becauseit is the same as the numberof times we
go through the inner loop of the assembly language delay routine (described in Example 4)
to produce a | millisecond delay. The delay in the C for loop will be somewhat longer
because the for loop gets compiled to assembly languageinstructions that take a few more
clock cycles than in the assembly language delay loop.
The inner for loop in the delay( ) function in Listing 2.8 will execute 500 times and,
each time through this outer for loop, the inner for loop will execute 5999 times. Thus, the
total numberof times through the inner loop before the de/ay(_ ) function exits will be 500 x
5999 = 2,999,500. The bus clock frequency of the microcontroller is 24 MHzso,if the inner
for loop took 4 clock cycles, then the total delay time will be
4x 2,999, 500/24, 000,000 =0.5 seconds.
Parallel Ports - Outputs 17
Before the main program in Listing 2.8 we have included the delay( ) function
prototype declaration
The first void in this statement indicates that this function does not return any value to the
calling program. The second void in the parentheses indicates that there are no parameters to
be passed from the calling program to the function. All functions that you use in your C
programs must havea prototype declaration. These are often grouped together in a separate
A file, but you can also include them at the beginning of the program as we have donehere.
void main(void) {
PLLinit (); // set system clock frequency to 24 MHz
seg7 enable(); // enable 7-segment display
while (1) {
seg7_on(0x7F, 3); // switch on all segments of digit 3
delay();
seg7s_off(); // switch off all segments
delay();
void delay() {
ant 4,1
for(i = 0; i < 500; itt) {
for(j = 0; j < 5999; j++) {
}
The main programin Listing 2.8 first enables the 7-segment displays and then enters
a while loop. The C while loop has the following general form
while(expression) {
statements;
}
Whenthe while loop is executed the expression in the parentheses is evaluated, and if it is
true, the statements between the braces {...} are executed, and then the expression in the
parentheses is evaluated again. As long as the expression is true, the statements will be
18 Chapter2
executed again. When the expression becomes false, the while loop is exited without
executing the statements again. A value of zero for the expressionis taken to be false, and a
non-zero value is taken to be true. Therefore, in the statement whi/e(1) in Listing 2.8 the
expression is always true, so the while loop is never exited. We use this statement to
continually execute the statements within the while loop forever.
Within the while loop, wefirst turn on all segments of the 7-segment display on the
right-most 7-segment display on the DRAGON12-Plus-USB, delay approximately half a
second, turn off all segments of the 7-segment display, and then delay approximately half a
second again. This process repeats endlessly, causing the display to blink on andoff about
every second. Try it.
void main(void) {
PLLinit(); // set system clock frequency to 24 MHz
seg7_enable(); // enable 7-segment display
while (1) {
seg7_on(0x7F, 3); // switch on all segments of digit 3
msdelay (500); // delay
seg7s off (); // switch off all segments
ms delay(500); // delay
Parallel Ports - Outputs 19
OS eee eee, eee ——_—_ —_—e ee eee ee
Underthe Hood
the subroutine. In Listing 2.10 the two instructions puly and pulx will pull (or pop) the
values of Y and_.X from the stack. Note that the order of pulling values from the stack must
be the opposite of pushing the values on the stack. For each push instruction, there must be a
corresponding pull instruction, so that the return addresswill be left on the top of the stack
for the RTS instruction to use to return to the nextinstruction in the calling program.
The third instruction in the subroutine ms_delay transfers the value in D (the number
of milliseconds to delay) to the index register Y. Index register X is then loaded with the
decimal value 5999 (hex $176F). The loop
will then keep decrementing X until it becomes zero. Thus, each of these two instructions
are executed a total of 5999 times. The instruction dex takes one clock cycle and the
instruction bne takes 3 clock cycles. You can find these values in the CPUI2 Reference
Guide, available for download from www.freescale.com. Thus, the total number of clock
cycles used to execute this md2 loop is 5999x 4 = 23,996. For a 24 MHz clock, each clock
cycle will take 1/24,000,000=42x10~° seconds. Thus, the total time used to execute the
md2 loop is 23,996x42x10~’ = 0.99983 milliseconds. The outer md/ loop in Listing 2.10
20 Chapter 2
loads index register X with 5999 (2 clock cycles), executes the md2 loop (0.99983 ms),
decrements Y(1 clock cycle), and, if Y is not equal to zero, branches back to md/ (3 clock
cycles). These extra six clock cycles take 6/24,000 = 0.00025 milliseconds to execute,
Thus, each time through the md/ loop takes a total of 0.99983 + 0.00025 = 1.00008
milliseconds. Therefore, to delay 1 milliseconds, we just need to execute this loop 7 times,
but this is just the value that is in register Y,
In this example we will show how to turn on and off individual LEDs on the
DRAGON I12-Plus-USBboard. Wewill first show you how to dothis entirely in C, and then
we will provide newCfunctioncalls to dothis.
Recall from Fig. 2.2 that setting a bit high in Port B will turn on the corresponding
LED on the DRAGON1I2-Plus-USB. Thus,it will be important to be able to set a particular
bit in a register to | or clear a particularbit to 0.
Suppose you want to set bit 3 of Port B to one, while leaving all other bits
unchanged. You can do this by ORing PORTB with the mask shown in Fig. 2.6. Notethat
ORing a bit with a 0 will leave the bit unchanged, while ORing a bit with a 1 will force the
bit to be 1. From Table 2.5, we can do this by using either the C statement
where is the C operator for a bitwise OR operation. That is, each bit in PORTB is ORed
with the corresponding bit in the hex value 0x08. A shorthand way of writing the C
statement (2.1) is
Thus, statements (2.1) and (2.2) are equivalent where |= is called a shorthand assignment
operator. Other C operators and shorthand assignmentoperators are shownin Table 2.5.
Port B Register
7 6 5 4 3 2 1 0
[| PB7 | PB6 | PBS | PB4 | PBS | PB2 | PBI | PBO |PORTB
7 6 5 4 3 2 1 0
L oO [| oO Jy 0 YT 0 7, T [...0 . 0 [0 |Mask
Suppose now you wantto clear bit 3 of Port B to zero, while leaving all otherbits
unchanged. Youcan do this by ANDing PORTB with the mask shownin Fig. 2.7. Note that
ANDinga bit with a | will leave the bit unchanged, while ANDinga bit with a 0 will force
the bit to be 0. A C statementthat will do this is
or, using the shorthand assignmentoperator, we could use the equivalent statement
Port B Register
Zc z 6 5 4 3 2 1 0
[_PB7 | PBo | PBS | Pe4 | PB3 |, PB2 | PBi [| PBO |PORTB
7 6 5 4 3 2 1 0
| 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | Mask
Consider the C program shownin Listing 2.11 which first turns on LEDs 0, 2, and 4
in turn, and then turns them off in turn. We knowthat writing a | to the bit position in
PORTB corresponding to a particular LED (see Fig. 2.9) will turn on that LED. For
example, writing a 1 to PBO in Fig. 2.9 will turn on LED 0 and writing a 0 to PBO will turn
off LED 0. As we have seen the way to turn on LED 0 while leaving all other LEDs
unchangedisto first read PORTB, then OR it with the mask 00000001 (or 0x01), and finally
write the resulting value back in PORTB. The statement that will do this is
or, using the shorthand assignment operator from Table 2.5, we could use the equivalent
statement
PORTB |= Ox01;
as shownin Listing 2.11. Similar statements are used to turn on LEDs 2 and4.
To turn off LED 0, we would need to AND PORTB with 11111110 (or OxFE). We
can do this by using either the C statement
PORTB‘= PORTB & OXFE;
or, using the shorthand assignment operator, we could use the equivalent statement
PORTB &= OXFE;
22 Chapter2
as shownin Listing 2.11. Similar statements are used to turn off LEDs 2 and 4. Try this
program.
void main(void) {
PLLinit (); // set system clock frequency to 24 MHz
led_enable(); // enable LEDs
seg7disable(); // disable 7-segment displays
while (1) {
PORTB |= 0x01; // turn on LED 0
ms delay(500);
PORTB |= 0x04; // turn on LED 2
ms delay (500);
PORTB |= 0x10; // turn on LED 4
ms delay (500);
PORTB &= OxFE; // turn off LED 0
msdelay(500);
PORTB &= OxFB; // turn off LED 2
ms delay(500);
PORTB &= OxEF; // turn off LED 4
ms_delay (500);
Port B Register
€ 6 5 4 3 2 1 0 Bit #
[_ PB7 | Peo | PBS | PB4 | PBs | PB2 | Pei [ PBO |PORTB
7 6 5 = 3 20 1 0) LED
Instead of having to figure out the hex value to OR and AND with PORTB in
order to turn on oroff a particular bit we have written two assembly language routines
that are called by the C function calls shown in Table 2.6. These assembly language
routines are always available to you when you set up an LBE_DRAGON12-Plus-USB
stationery project. Listing 2.12 shows how you can modify the program in Listing 2.11
to produce the sameresult. Try it.
Parallel Ports - Outputs 23
Table 2.6 C function calls for turning on or off a single bit of PORTB
C Function Call Meaning
led on(int b); Sets bit b of PORTB high
led off(int b); Sets bit b of PORTB low
void main(void) {
PLLinit); // set system clock frequency to 24 MHz
led_enable(); // enable LEDs
seg7disable(); // disable 7-segment displays
while (1) {
led_on(0); // turn on LED 0
ms delay(500); // half-second delay
led_on(2)j; // turn on LED 2
ms_delay (500); // half-second delay
led_on (4); // turn on LED 4
ms_delay (500); // half-second delay
led_off(0); // turn off LED 0 "
msdelay (500); // half-second delay
led_off(2); // turn off LED 2
ms_delay (500); // half-second delay
ledoff (4); // turn off LED 4
msdelay (500) ; // half-second delay
}
}
; led_on( b# )
: set bit number b# of PORTB to 1
led.on:
pshy
ldy # MASK
aby
ldaa 0,Y
oraa PORTB 7;OR mask with PORTB
staa PORTB ;Sstore back in PORTB
puly
rts
; led_off( b# )
; clear bit number b# of PORTB to 0
led_off:
pshy
ldy #MASK
aby
ldaa 0,
coma ;complement mask
anda PORTB ;AND mask with PORTB
staa PORTB ;store back in PORTB
puly
rts
In this example we will show how to have the each of the 7-segment displays on the
DRAGON 12-Plus-USB board count in hex from 0 to F continually. We will first show you
how to do this entirely in C, and then we will provide a new C function to display any hex
digit.
eeeS Le oS ~~ tes <=: \
Wehave seen in Fig. 2.3 that the DRAGON12-Plus-USB board has a common-
cathode 7-segment display connected to Port B. This means that on the DRAGON 12-Plus-
USBa turns a segment on and a 0 turns a segmentoff.
The table shown in Fig. 2.9 showsthe output values for each segmenta — g needed to
OY
g code
DORPCOORPRPRPRFPOORP RH FP BID
ZAAQRATP WO DIDUBWNHeE Ol”
PRrRrRrPrPrPOrFOrROOOFOFR!0
PrRrOrRPORPRFPRPRFPFPFOFRF OF ]p
0 3F
0 06 a
1 5B Ce)
1 4F
1 66
1 6D
g
Seeeeee 8 OS eee eeee ee
1 7D —
0 07
1 TE | Cc
1 6F
1 77
1
Co)
7C
d
0 39
1 SE
1 79
1 7]
Listing 2.14 shows how wecancreate a table of the hex codesin Fig. 2.9 byusing a
constant character array called seg7tbi[]. The type declaration char defines each element of
the array seg7tbi[] to be an 8-bit byte. The type qualifier const defines each of these array
elements to be a constant that can’t be changed in the program. Note howthe braces {..} are
used in the array definition to define the 16 constant hex codes given in Fig. 2.9. Also note
that the type declarations for seg7tb/[] and the integer i used in the for loop must precede the
function call PLL_init( ).
In the while loop in Listing 2.13 there is afor loop with an index / that goes from0 to
15. Each time through this for loop the output of Port B is set to seg7th/[i], which will
output the proper hex code correspondingto the hex digit i. Note that the square brackets [ ]
are used to indicate array elements in C. The secondstatement in the for loop is a half-
second delay before the next digit is displayed. Once all 16 digits have been displayed the
while(1) loop will just keep counting again. Try it. Note that all four 7-segment displays
count from 0 — F in unison. Wewill next see howto display a hex values on a single 7-
segmentdisplay and then, in Example 7, show howto display different values on each of the
7-segmentdisplays.
Instead of having to make your own 7-segment decoder table, we have written
an assembly language routine that is called by the C function call seg7dec(int i, int 5)
shown in Table 2.7. This assembly language routine is always available to you when
you set up an LBE_DRAGON12-Plus-USBstationery project. Note that this function
26 Chapter 2
will display the hex value j on the 7-segment display number }, where b = 0 is theleft-
most 7-segment display and 5 = 3 is the right-most 7-segment display. Listing 2.15
showsa programthat hasthe right-most 7-segment display count from 0 to F. Try it.
seg7tbl[] = {
Ox3F,0x06, Ox5B, 0x4f,
0x66, 0Ox6D, 0x7D, 0x07,
Ox7F,Ox6F,0x77,0x7C,
0x39, 0x5E,0x79, 0x71
;
t $j
(D
// disable LEDs
Ql
le();
t-
|}:
mw
Fl
=m
'
@M
=
+
—_
F-
Oo
nH
B = seg7tbl [i];
O
ro
8)
MO
4
void main(void) {
int i;
PLL init(); // set system clock frequency to 24 Mxz
seg7_enable(); // enable ?-segment display
led_disable(); // disable LEDs
while (1) {
for(i = O; i < 16; itt) {
seg7dec (i, 3);
ms_delay (500);
7-segment decoder
a seg/dec(int digit, int digit#);
1t# 1s in D
is at 2,sp (index into SEG7TBL)
~.
pshy
ldy #MASK
aby
ldaa O,y 7A = mask
coma
staa PTP renable digit digit#
ldd 2,.8p 7B = digit
ldy #SEG7TBL
aby 7yY -> 7-seg code
ldaa 0,y
staa PORTB
puly
rts
ms, which correspondsto a blinking rate of 50 Hz. Your cyes can’t see things blinking
at that rate. Try out the program.
void main(void) {
const char digits[] = {
1,2,3,4
};
int i;
PLL_init(); // set system clock frequency To 24 MS
seg7_enable(); // enable 7-segment display
led_disable (); // disable LEDs
while (1) {
for(i = 0; i < 4; i++) {
seg7dec(digits[i],1i);
msdelay(5);
PROBLEMS
2.1 Modify Listing 2.1 to turn on all segments andall LEDs but enable onlythe twocenter
7-segment displays.
2.2 Modify Listing 2.1 to turn on the four left-most LEDs.
2.3. Modify Listing 2.1 to turn on the two LEDsoneachend.
2.4 Modify Listing 2.6 to tum onall segments.
2.5 Modify Listing 2.6 to display the letter L.
2.6 Modify Listing 2.6 to display the letter H.
2.7. Modify Listing 2.6 to display theletter P.
2.8 Modify Listing 2.6 to display the letter A.
2.9 Modify Listing 2.6 to display the letter E.
2.10 Modify Listing 2.9 to blink a 2 ondigit 3 every 2 seconds.
9.11 Modify Listing 2.9 to blink a 5 on digit 0 every 0.5 seconds.
4.12 Modify Listing 2.9 to blink a | on digit 2 every 0.25 seconds.
9.13 Modify Listing 2.9 to blink a 3 on digit | every 3 seconds.
seconds.
9.14 Modify Listing 2.9 to blink a 5 ondigit 3 every 0.2
seconds.
9.15 Modify Listing 2.9 to blink a 4 ondigit | every4
30 Chapter 2
2.16 Modify Listing 2.12 to tum on LEDs1, 3, 5 in sequence and thenturn themoff in the
Same sequence.
ModifyListing 2.12 to tum on LEDs7, 6, 5 in sequence and then turn themoff in the
Same sequence.
2.18 ModifyListing 2.12 to turn on LEDs0,3, 4, 7 in sequence and then turn them off in
the same sequence.
Modify Listing 2.12 to turn on and off each LED in sequence from rightto left.
NNN NNN NN NN
COomn~an Ss tide i Nm OO
Nm Nh th ~ Ww ty te lp bo me
Modity Listing 2.12 to turn on and off each LED in sequencefrom left to right.
Modity Listing 2.15 to count only the even hexdigits.
ModifyListing 2.15 to count only the odd hex digits.
ModifyListing 2.15 to count downfromF to 0.
ModifyListing 2.15 to count down only the even hex digits.
=~ ModifyListing 2.15 to count down onlythe odd hexdigits.
ModifyListing 2.15 to count only hex digits that are divisible by 3.
ModifyListing 2.17 to display the hex number 93AF onthe 7-segmentdisplays.
ModifyListing 2.17 to display the word HELP onthe 7-segmentdisplays.
Write a program that will have the LEDs on the DRAGON 12-Plus-USB board countin
binaryin steps of 1 every 0.5 seconds.
Write a program that will have the LEDs on the DRAGON12-Plus-USB board countin
)
to
S
Chapter 3
The DRAGON 12-Plus-USB board contains four pushbuttons and eight DIP switches.
Pushbutton switches S2 — S5 on the DRAGON |2-Plus-USB board are connected to bits 3 — 0
of Port H as shown in Fig. 3.1. If the switches are not being pressed the 100 k© pullup
resistors will cause the voltages at pins 3 — 0 to be 5 volts andtherefore a read of Port H will
read these bits as 1. Closing a switch will cause the input to that bit of Port H to be grounded
and therefore that bit will read 0 when Port H is read.
The DIP switch SW1 is connectedto the same Port H as shownin Fig. 3.2. Note that
the rightmost four DIP switches share the same lower four bits of Port H with the four
pushbutton switches. Also note that the DIP switches are connected to ground through 4.7
kQ resistors. These are low enough compared with the 100 kQpullup resistors that a closed
switch will still read a zero.
Clot Lt
Ly} 4
= = <s Ss < . 100 kKQ
pt ||
PH7 PH6 PHS | PH4 | PH3 | PH2 PH! PHO Pork
|
| sw2 . sw3 SW4 SW5
In Example 8 we will showhowto read the DIP switch SWI and the four pushbutton
switches, SW2, SW3, SW4 and SW5, on the DRAGON 12-Plus-USB board. We will first
show youhowto dothis in C, and then we will provide new C function calls to dothis.
32 Chapter 3
re ne +5V
“Say
<
Say,
os
a
ye
“sS
=
~
S
<=
S
<<
Ss
of 100 kQ
PHT .
PH6 - “PHS”[PH4 LFPH3 :
H
2_|PHI 0
—A\\\y
'
Swi
we
Ppp
Li bia
—VW
MIN
void main(void) {
PLL_init(); // set system clock frequency to 2
seg7 disable(); // disable 7-segment display
led_enable(); // enable LEDs
DDRH = 0x00; // Port H inputs
while (1) {
leds_on(~PTH)-;
}
void main(void) {
PLLinit ()7 // set system clock frequency to
seg7_ enable(); // enable 7-segment display
leddisable (); // disable LEDs
DDRH = 0x00; // Port H inputs
while (1) {
while((PTH & Ox01) == 0) { // while pressing |
seg7dec (5, 3); // display 5 on dig
}
seg/s off (); f/f turn off all seq
while( (PTH & Ox02) == QO) { f/f while pressing SW4
seg7dec (4,2); // display 4 on digit
}
seg7s off (); /f turn off all ?-seq
while( (PTH & Ox04) == Q) | // while pressing Sw
seg7dec (3,1); // display 3 iq
}
f/f turn off all ?-seq
seg/s_ off ();
while( (PTH & Ox08) == QO) { f/ while pressing SW.
seq/dec (2,0)? fy display - r chi
}
turn off al } displ iys
seg7s off ();
34 Chapter 3
will be true if bit 0 of PTH is 0, 1.e., if switch SWS5 is being pressed. Similarly, the
expression
void main(void) {
PLL:init () ; // set system clock frequency to 24 MHz
seg7 enable(); // enable 7-segment display
led_disable(); // disable LEDs
SW_enable(); // enable switches
While (1) {
while (SW5_down()) { // while pressing SW5
seg7dec (5,3); // display 5 on digit 3
}
seg7soff (); // turn off all 7-seg displays
while (SW4 down ()) { // while pressing SW4
seg7dec (4,2); // display 4 on digit 2
}
seg7s_ off (); // turn off all 7-seg displays
while (SW3_down()) { // while pressing SW3
seg7dec (3,1); tt display 3 on digit 1
}
seg/7s_off (); // turn off all 7-seg displays
while (SW2_ down ()) { // while pressing SW2
seg7dec (2,0); Lf display 2 on digit 0
}
seg/s_off (); // turn off all ?-seg displays
}
}
As shownin Figs. 3.1 and 3.2, the four pushbutton switches, S2 — $5, and the DIP
switch, Sl, on the DRAGON12-Plus-USB are connected to Port H (PTH). The assembly
language routines for the ten SW/-SW5 C functions shownin Table 3.2 are shownin Listing
3.4. Note that the first subroutine, SWenable, will clear all bits in the data direction register
for Port H, thereby all bits of Port H inputs. The subroutine SW/_dipwill read the status of
the DIP switches by returning the contents of PTH in accumulator D.
s
accumulator D when switch SW2is being pressed, i.e., when the switch is closed. The first
two instructions will clear accumulator D, making its value fa/se ($0000). The next
=
instruction is
~~
eeae ey
This branch onset instruction will AND the complementofthe bits in P7H with the mask,
$08 = 00001000, and branch to SW2end/ ifthe result is zero. That is, if the original bit 3 of
PTHis |, meaning pushbutton switch SW2 is open andnot being pressed, then the program
36 Chapter 3
branches to SW2end/, which is an rts instruction that returns the value $0000 (false) in
accumulator D. On the other hand, if SW2 is being pressed, then bit 3 of PTH will be 0, and
the ANDing of the complement of the bits in PTH with the mask, $08 = 00001000 will not
be zero, so the branch will not be taken and the next instruction, /dd #$FFFF, will be
executed. This instruction will load the value $FFFF (true) into accumulator D before
returning to the calling program.
The subroutine SW2_up in Listing 3.4 will return a srue value (S$FFFF) in
accumulator D when pushbutton switch SW2is not being pressed, i.e., when the switch is
open. This subroutine 1s very similar to the SW2_down subroutine except that the brset
instruction is replaced by the branch onclearinstruction
As you might expect, this instruction will AND the bits in PTH with the mask, $08 =
00001000, and branch to SW2end]/if the result is zero. Thatis, if bit 3 of PTH is 0, meaning
pushbutton switch SW2is closed and being pressed, then the program branches to SW2end1/,
which is an rts instruction that returns the value $0000 (fa/se) in accumulator D. On the
other hand, if SW2 is being pressed, then the instructions following the brc/r instruction will
be executed, returning a value of $FFFF (trve) in accumulator D to the calling program.
The remaining subroutines in Listing 3.4 work exactly the same ways as the
subroutines SW2_down and SW2_up except that the masks used in the brset and brcir
instructions are different, correspondingto bits 2, 1, and 0 of PTH.
SW1 dip:
clra
ldab PTH ;Read Port H
rts
The DRAGON 12-Plus-USBhasa built-in 4x4 hex keypad that is connected to Port
A of the microcontroller as shown in Fig. 3.3. Note that pins PAO-PA3 are configured as
outputs and pins PA4—PA7 are configured as inputs. These four inputs are pulled up to 5
volts with four internal pull-up resistors. The C statement PUCR = 0x01 will enable these
pull-up resistors. Thus, if all the key switches are open, the four bits PA4—PA7 will all be
read as |'s. Ifa zero is written to only one of the inputs PAO—PA3 (one columnin Fig. 3.3),
then a key in that columnthat is pressed will cause the input connectedto its row to go low.
This can be read by the MCU to determine which key has beenpressed.
For example, in Fig. 3.3, suppose that PA/ is brought low while PAO, PA2, and PA3
are high. That is, a 1101, or OxD, is written to the low nibble (lower 4 bits) of Port A. If
Port A is then read and the high nibble, PA4—PA7,is not OxF, then either key 2, 5, 8, or 0,
must have been pressed. If PA4 is low,i.e. Port A reads OxED, then key 2 was pressed. If
38 Chapter 3
PASis low, i.e. Port A reads OxDD, then key 5 was pressed. If PAG is low, i.e. Port A reads
OxBD, then key S was pressed. If PA? is low, i.e. Port A reads 0x7D, then key 0 was
pressed. In a similar way we could determine the key codes for all 16 keys and store them tn
a table called devooges as shown 1n Table 3.3.
~< PAO
“<« PAI
~« PA2
1 2} 3 A OR
oe oe oe
t t Sa 8“ wana
$} 5} 6) 8;
oe oe oe o-~m
t tit me PAS
1 8} 9} cy
o~< oe oe Oo 2
t fey f 3 OP pas
Figure 3.3 Connecting the 4 x 4 keypad on the DRAGON12-Plus-USBto
Port A
The C function kev_scan( ) given in Listing 3.5 reads each of the 16 codes in the
table keycodes, stores the code in PORTA, andthen reads back the contents of PORTA. The
key value is found when the read back value is equal to the key code. Note that key_scan()
returns a value of 16 if no key is being pressed. The constant keycodes[ ] array given in
Listing 3.5 1s from Table 3.3. The C function ger_kev( ) shownin Listing 3.6 will wait for a
Key to be pressed and return the hex value of the key pressed. Note that it does this by using
the C do-while looping statement that will continue to loop as long as key_scan( ) returns a
value of 16; 1.¢. as long as no keyis being pressed.
Once you obtain a keypad value using the word gerkev( ) you usually want to do
something with this value such as display the hex digit on a liquid crystal display (LCD) that
will be described in Chapter 4. If, for example, you want to display the value ofthe first key
pressed at the current cursor position on an LCD, and then display the value of the second
key pressed at the next cursor position, you could run into a problem. After displaying the
first value, if your finger was still pressing the key, the program would display this same
value at the next cursor position, In fact, the first digit would streak across the LCD display
as long as you keep your finger down! Youneed to be able to wait until you have released
your finger before waiting to press another key. The C function waitJor_keyup( ) in Listing
Paralio’ Ports - treats 39
3.6 will do this. The C program mrain( ) shown m Listing 3.7 will display any key you press
on the right-most 7-segmentdisplay of the DRAGON 12-Pius-USB
an
found = 1; Sf and exit 1
}
else
next «x
betesot eee
Let? fé else check
}
return key; ff vt
void main(void) {
int c;
PLLinit (); // set system clock frequency to 24 MHz
seg7 enable(); // enable 7-segment display
leddisable(); // disable LEDs
DDRA = OxOF; // Port A: A7:A4 inputs, A3:A0 outputs
PUCR = 0x01; // enable pullup resistors
while (1) {
c = get_key();
seg/dec (c, 3);
wait for keyup();
Sometimes the switches making up a keypad will have a tendency to bounce when
they are pressed. That is, when contact is first made, it may open momentarily before
closing for good. This could lead to thinking that the key was up (and therefore continuing
the program) whenit really wasn't. In such a situation a digit might inadvertently get
displayed twice. To solve this problem, key switches are debounced, either in hardware or
software. The software solution is to delay for about 10 msafter a key pressing is sensed. If
the key is read again, and it is the same value as before, then you can conclude that the key
has stopped bouncing and the correct value has been read.
We have written assembly language routines for reading the 4x4 keypad, which
include the debounce delays. (The assembly language routines to read pushbutton switches
SW2 to SW5 described in Example 8 also include debounce delays.) The C function calls to
access these routines are shown in Table 3.4. The keypad_enable( ) routine will proper
ly set
the data direction register and enable the pull-up resistors of Port A. An
example of using
these routines to display the key value pressed on the right-most 7-segm
ent display of the
DRAGON 12-Plus-USB is givenin Listing 3.8.
Parallel Ports - Inputs 41
void main(void) {
char C;
PLL init(); // set system clock frequency to 24 MHz
leddisable(); // disable leds
seg7 enable(); // enable 7-segment displays
keypadenable(); // enable keypad
while(1) {
c = getkey();
seg7dec(c, 3);
wait_keyup();
}
The assembly language routines for the four hex keypad C functions shown in Table
3.4 are shownin Listing 3.9. Thefirst instruction in the subroutine keypad enable will store
the hex value $OF in data direction register A. This will make PA7 — PA4 inputs and PA3 —
PAO outputs as shown in Fig. 3.3. The pull-up control register (PUCR) shown in Fig. 3.4
can be used to enable internalpull-up resistors on pins that have been configured as inputs on
Ports A, B, E, and K. (This register affects only pins 7, 4-0 on Port E). Thus, to enable
pull-up resistors on the inputs PA7 — PA4in Port A, we need to write a 1 to bit 0 of PUCR.
We can dothis with the instruction
The subroutine keyscan in Listing 3.9 is the assembly language version of the C
function key_scan( ) in Listing 3.5, which will scan all 16 keys on the 4x4 keypad. If no
key is being pressed, a value of 16 ($10) is left in accumulator D to be returnedas the value
of the C function keyscan( ) in Table 3.6. Ifa key is being pressed, then the value returned
in accumulator D will be the hex value of the key being pressed.
42 Chapter3
)
; wait to lift finger from key (with debounce
wait_keyup:
bsr keyscan ;scan keypad
cmpb #$10 ;while key is pressed
bne wait_keyup
ldd #10
jJsr msdelay ;delay ~10 ms
bsr keyscan ;scan keypad
cmpb #$10 ;if key is pressed
bne wait_keyup ; repeat
rts
The next instruction in the subroutine, keyscan, in Listing 3.9 1s /daa b,x. This will
load into accumulator A the byte at the address formed by adding B to X. This will be the
first byte in the keycodes table. The keycode is stored in PORTAand the upperfour bits of
this code are saved in the variable temp. This byte variable is defined at the beginning ofthe
file main.asm using the statement
temp: xrmb 1
The directive rmb meansreserve memory byte, and will reserve | byte in memory to hold the
value of temp. You can reserve any numberofbytes using rmb. For example,
buff: rmb 12
will define a buffer containing 12 bytes with buffbeing the address of the first byte in the
buffer.
The ks2 loop in Listing 3.9 is a short delay of about 40 clock cycles to allow the
voltages on Port A to stabilize before reading back the contents of PORTA. This value is
then ANDed with $FO and compared with the upper four bits of the keycode that was saved
in the variable temp. If the value read from PA7 — PA4is equal to the value in temp, then
the program jumps to As3 where accumulator 4 is cleared and 8 will contain the hex value of
the key being pressed. On the other hand, if the value read from PA7 — PA4is not equal to
the value in femp, then the value of B is incremented, andifit is not equal to 16 ($10), the
program branches back to ks/ where the next keycodein the table is tested. Note that if no
44 Chapter 3
key is being pressed. all Aevcodes will be tested before the subroutine exits with a value in B
equal to 16 (S10).
The subroutine Aevpad in Listing 3.9 continually calls the subroutine keyscan until
the value in B ts something other than 16 ($10), i.e., until a key has been pressed. The
subroutine gevker in Listing 3.9 is the assembly language version of the C function get_key( )
in Listing 3.6, except that we have debounced the keys by adding a 10 ms delay. Wefirst
wait unt] a key has been pressed bycalling keypad, and then we save the key value by
pushing 8 onto the stack. After delaying 10 ms, we call keypad again. If the key has
stopped bouncing, the value in B should be the sameas the first value we pushed on the
stack. We can compare these two values bypulling this first value into A, and subtracting B
from 4. Ifthe result is zero, we have our debounced key valuestill in B and weexit the
subroutine. If the values are not equal, we go back and repeat the process until they are
equal.
Finally, the subroutine wait_keyup in Listing 3.9 is a debounced assembly language
version of the C function wait_for_keyup( ) in Listing 3.6. This subroutine continually calls
the subroutine Aeyscan until the value in B is 16 ($10), i.e., until a key has been released.
After delaying 10 ms, the subroutine then checks to makesure that the keyisstill up.
PROBLEMS
3.1 Modify Listing 3.1 to have the DIP switches on the DRAGON12-Plus-USB turn on the
corresponding segments of the 7-segment displays. Hint: Enable the 7-segment
displays and disable the LEDsin Listing 3.1.
3.2 Write a program that will toggle a 5 on and off on digit 3 of the 7-segment displays
when pushbutton SWS is pressed on the DRAGON 12-Plus-USB. Thatis, the first time
the button is pressed a 5 is displayed on the 7-segmentdisplay and stays displayed
whenthe button is released. The second time the button is pressed, the display goes
off. The third time the button is pressed the 5 is displayed again.
Liquid Crystal Displays 45
Chapter 4
In this chapter we will show how to write characters to the liquid crystal display
(LCD) on the DRAGON 12-Plus-USB board. We will provide newC function calls to make
it easy to dothis.
RS R/W Operation
SBI
0 0 Write instruction code
+
Read busy flag and address counter
—g
White data
Read data
The signal, RS, can be thoughtofas a register select signal that selects either the LCD
control register (RS = 0) or the LCD data register (RS = 1). The read/write signal R/W is |
for a read operation and 0 for a write operation. Data or instruction codes are written on the
falling edge of E, and £ must be high for a read operation.
The HD44780 hasits own instruction set shown in Table 4.1. (For a complete data
sheet go to Attp:/Avww.hitachi.com/.) Thefirst eight are instruction codes that are written to
the LCD control register with RS = 0 and R/W = 0 as shownin Fig. 4.1. The last entry in
Table 4.1 shows the format of the busyflag and address counter when reading from the LCD
control register with RS = 0 and R/W = 1 as shown in Fig.4.1.
The HD44780 contains a 128-byte data display memory (DD RAM)that contains the
ASCII codes of the characters being displayed on the LCD display. This DD RAM address
is set (address 0 is the display homeposition) using the Set the DD RAM addressinstruction.
After this is done, subsequent data writes will write the ASCII code of the character to be
displayed to the DD RAM address (and display the character) and then increment the DD
RAM addressso that the next character will be displayed in the next location.
The HD44780 also contains a 64-byte character-generator memory (CG RAM)that
can be used to change the font of up to 16 different characters. If you are interested in doing
this you can consult an HD44780 data sheet.
The data bus DB0O-DB7 on the HD44780 can be connected directly to a
microcontroller's data bus and the controller can be wired up to respond to reads and writes
to a particular series of addresses. The HD44780 can also be connected to the parallel I/O
ports on a microcontroller and then software can be written to produce the control signals
shown in Fig. 4.1. This is the approach taken on the DRAGON 12-Plus-USB board.
The diagram in Fig. 4.2 shows how the LCD connector on the DRAGON12-Plus-
USB board is connected to Port K of the MC68HCS912DG256. Notethat only the upper 4
bits of the LCD controller data bus are connected to PTK[5:2] while the enable signal E is
connected to PK/ and theregister select signal RS is connected to PKO. The read/write line
is connected to PK7 through the header J5 on the DRAGON12-Plus-USB board. This
J5
header can also connect the read/write line directly to groundto provide write-only
operation
Liquid Crystal Displays 47
of the LCD display. In this case, you will not be able to read the busy flag. This is not a
disadvantage because reading this flag is often problematic and an alternative is to simply
delay after writing to the LCD. This is what wewill do.
Before you can write to the LCD you mustinitialize it. We have provided the C
function call LCD_init( ) to do this. This function initializes it for 4-bit, 2 line, 5 x 7 dot
Operation, display on, cursor off, no blinking, then clears the display and sets the cursor to
the homeposition.
You canset the cursor to any position on the display by calling the built-in C function
call set_lcd_addr(char ad) where adis an 8-bit hex address whose display position is shown
in Fig. 4.3. Note that there is a gap betweenthe endof the first line and the beginning ofthe
secondline.
00 Ol 02 03 04 05 06 07 os [09 0A 0B OC OD OF OF
40 41 42 43 44 45 46 47 48 49 [4a [4B [4c 4D 4E 4E
A list of all the C function calls that we have provided assembly language routines
for are shownin Table 4.2. Listing 4.1 shows how to display a message on eachline of the
display. Try it.
The statement char* q1; in Listing 4.1 defines g1 to be a pointer, i.e., an address to a
memory location of type char(i.e., a byte). The statement gl = "Microcontrollers"; then
gives the value of g! to be the address of the first character in the string "Microcontrollers".
The C function /ed_init( ) will initialize the LCD. You must call this function before the
LCD canbe used. The statement set_/cd_addr(0x00) will movethe cursor to the beginning
of the first row of the LCD as shownin Fig. 4.3. The function type_/cd(q1) will then write
the entire string g] on the LCD.
void main(void) {
char* gl;
char* a2;
ql = "Microcontrollers";
q2 = "are FUN";
PLL_init(); // set system clock frequency to 24 MHz
The function data&(char c) in Table 4.2 will display the character whose ASCII code
is passed as a parameter. The ASCII codesofall characters are shownin Table 4.3. In this
Liquid Crystal Displays 49
table, the upper nibble of the ASCII code is given by the column heading, and the lower
nibble is given by the row number. For example, the hex ASCII code for upper-case A 1s
0x41 and the ASCII code for lower-case k is 0x6B. Thus, the function call data8(0x41) will
display an upper-case A at the current cursor position. The cursor is automatically
incremented whenthis functionis called.
The function hex2/cd(char c) in Table 4.2 will display the hex digit passed as the
parameter on the LCDdisplay. For example, hex2/cd(OxA) will display the A on the LCD
display by first converting OxA to the ASCII code 0x41 using the built-in function
hex2asc(char c). The cursor is automatically incremented whenthis function is called.
byte in the table. The instruction jsr write_instr_nibble jumps to the subroutine
write_instr_nibble, which will shift the 4-bit nibble 2 bits to the right so that it will end up in
PKS — PK2 as required in Fig. 4.2. The write_instr_nibble subroutine then brings EF (PK1)
high and then low(after a short delay) with RS (PKO) equal to zero. This satisfies the
Instruction write condition in Fig. 4.1.
The rest of the subroutine init_/cd in Listing 4.2 loops through the entire init_codes
table, writing each instruction to the LCD with a 5 ms delay between writes. While this
delay is longer than required for some of the instructions, it will satisfy the worst case
condition, and makes the programming easier by using a simple loop.
; clear LCD
clearled:
ldaa #$01
jsr writeinstrbyte
ldd #10
jsr ms delay
rts
In this section we will show howa binary number can be converted to an ASCII
string that can be displayed on an LCD display. To display the value of a 16-bit integer(in?)
or a 32-bit long integer (/ong) on a computer screen or LCD displayit is first necessary to
convert this integer to a string of ASCII characters. The steps used to create this string of
ASCII characters are illustrated in Fig. 4.4, Note that the algorithm consists of dividing the
numberby the base, and converting the remainder to an ASCII character.
Figure 4.5 showsthe algorithmfor a routine called sharps which will convert a 32-bit
double numberto an ASCII string according to the steps in Fig 4.4. Note that the index pad
starts at the end of the buffer, buff, and gets decremented before storing each ASCII code in
54 Chapter 4
the buffer. When the entire double number has been converted buff[pad] will contain the
first ASCII character in the numberstring.
12/10 = 1 Rem = 2 — 33
1/10 = 0 Rem = 1 > 34
PAD —>>
Figure 4.4 Steps for creating an ASCII number string
SHARPS: convert the double number va/32 to an ASCII string in a given base. The
digits are converted leastsignificant digit first and stored in memory starting at the
end of the string. If the base is 16 then 0x37 must be added to the remainder to
obtain the ASCII codes for A — F.
integer, right-justified in a field of 10 digits. Listing 4.3 shows an example of using these
functions. Try it.
int vall6;
long val32;
void main(void) {
PLLinit (); // set system clock frequency to 24 MHz
led_inait ();3 // enable lcd
vall6 = 54321;
set_lcd_addr (0x00); // display S-digit int
writeint_lcd(vall16);
val32 = 2345678123;
set_lcd_addr (0x40) ; // display 10-digit long
writelonglcd(val32);
while(1) {
}
The assembly language routines for the two integer display C functions shown in
Table 4.3 are given in Listing 4.4. The constant bas10 is equal to the base 10. Four bytes
are reserved for the 32-bit variable dnum, and 12 bytes are reservedfor the buffer buff. The
address padis thefirst address after the end ofthe buffer buff.
When the C function writeintIcd(int ”) in Table 4.3 is called, the subroutine
write_int_Icd in Listing 4.4 is executed. The integer 7 to display on the LCDis passed to the
subroutine in accumulator D. After filling the buffer (called pad) with blanks (ASCII $20),
the integer in D is stored in the lower two bytes of dauwm, with the upper two bytes filled
with zeros. Then the binary number to ASCII string conversion is performed bypointing to
the address pad with index register X, and calling the subroutine s/arps.
56 Chapter4
; blank pad
blankpad:
ldx #buff
ldab #13
ldaa #520 ;ascii blank
bpl: staa 1,x+
decb
bne bpl
rts
Liquid Crystal Displays o7
Listing 4.4 (cont.) Write Integer Assembly Language Subroutines from main.asm
; double division 32 / 16 = 32 16 rem
; numH:numL / denom = quotH:qoutL remL
;Y¥:D / X= Y:D rem X use EDIV twice Y:D/ X= Y rem D
ddiv:
pshd ;save numL
tir y,d ;d = numH
ldy #0 ;O0:numH / denom
ediv 7y = quotH, d = remH
bec ddl ;if div by 0
puld
ldd #SFFFF squot = SFFFFFFFF
crr d,y
tir d,x ;rem = SFFFF
rts
ddl sty 2,—-Sp ;save quotH on stack
tir d,y 7;y = remH
ldd 2, 8p 7d = numb
ediv zremH:numL/denom Y = quotL D = remL
t£yr a,x 7x = remL
tir y,a ;ad = quotL
puly 7;yY = quotH
leas 2,sp ;f£ix stack
rts
The subroutine sharpsin Listing 4.4 implements the algorithm shownin Fig. 4.5. It
doesthis by repeatedly calling the subroutine sharp until the quotientleft in dnum is equal to
zero. The subroutine sharp converts the next digit in the conversion by dividing dnumby the
base, converting the remainder to ASCII, and storing this ASCII code in the nextlocation in
the buffer, which is being pointed to by X(see Fig. 4.4). The division is carried out using
the subroutine divin Listing 4.4. This division divides a 32-bit double integer (long) by a
16-bit integer, leaving a 32-bit quotient and a 16-bit remainder. Recall from Appendix C
that the unsigned division instruction ediv will divide the unsigned 32-bit number (Y:D) by
the unsigned 16-bit number X, and store the unsigned 16-bit quotient in Y and the unsigned
remainder in D. The problem is, that if the divisor in X is too small then the quotient won't
fit in Y, but could be as large as 32 bits. We solve this problem by using ddiv, whichcalls
ediv twice,first by dividing
(0: numH )/denom=quotH remH
and then dividing
(remH > numL )/denom = quotL remL
Note in the subroutine ddiv the value of quotH, which is in Y after the first ediv
instruction, is saved on the stack using the instruction sty 2,-sp. This pre-decrement,
indexed addressing will first decrementthe stack pointer, sp, by 2 and then store the value of
Y in memory at the address pointed to by sp. At the beginning of the subroutine we pushed
D on the stack so that we could access mumL later. When weneedit later, the value of Y is
on the top of the stack, so we use the instruction /dd 2,sp to get the value of D from the
stack. At the end of the subroutine, we pull the value of Y from the top of the stack (which
contains the value of quotH), but the original value of D is still on the stack. We can't just
remove this value from the stack by pulling it into D, because D now contains the correct
value of quotL. We need to add 2 to the stack pointer so that it will be pointing to the
subroutine return address. We can do this using the /oad effective address instruction leas
2,sp, Which loads into the stack pointer, sp, the effective address of the addressing mode
used. In this case, 2,sp adds 2 to sp, so the effective address is sp+2. Thus, the instruction
leas 2,sp is equivalent to sp = sp +2. .
After calling the subroutine sharps in the subroutine write_int_/lcd in Listing 4.4,
there will be a maximumof five ASCII codes stored at the end of the buffer, buff. This 1s
becausethe largest 16-bit unsigned number is 65535. Thus, the first character in the number
string will be located at pad —5. Weloadthis address into X and then load each ASCII code
in turn into B using the instruction /dab 1,x+, and then display it on the LCD using the
subroutine data8. Note that X gets incremented by 1 each time we load B with the next
ASCII code. We stop the loop when X is no longer Jess than pad. Note that we use the
branching instruction d/o rather than bne, because we want to compare unsigned addresses
and bne would treat the addresses as signed numbers and could therefore not exit the loop
properly.
Whenthe C function write_long_Icd(long d) in Table 4.3 is called, the subroutine
write_long_Icd in Listing 4.4 is executed. The 32-bit long integer d to display on the LCD is
passed to the subroutine in X:D. Thatis, the high 16-bits of d are passed in index register A,
and the low 16-bits of d are passed in accumulator D, The operation of this subroutine is
very similar to the subroutine write_int_Icd, except that a maximum of10 digits are stored in
Liquid Crystal Displays 59
the buffer, pad, and displayed on the LCD. This is because the largest unsigned value that
can bestored in 32 bits is 4294967295.
;
;
4.3 ASCII NumberString to Binary Conversion
| In this section we will show how an ASCII numberstring can be converted to a
binary number and use it to enter binary numbers from a keyboard or keypad. As an
=T:)DhCtC~<S;S SESE
Wehave implemented this algorithm in assembly language, which you can call using
the C function call shown in Table 10.1. In your C programyouwill have an ASCII number
string stored in an array pointed to by the pointer prr. This ASCU numberstring will be
terminated by some character other than the ASCII codes for 0 — 9 (Ox30 - 0x39). The
algorithm continues to convert the ASCII string to a long integer as long as valid digits are in
the string. It terminates with the first invalid digit. Thus, if you call nuwmber(prr) it will
return a long integer (32 bits) whose value is equal to the ASCII numberstring.
60 Chapter 4
Whenthe C function number(char* ptr) shown in Table 4.4 is called, the subroutine
number shown in Listing 4.5 is executed. When this subroutine is called, accumulator D
contains the value of the pointer prr, i.e., it contains the address of the first ASCII character
in the ASCII numberstring. This value is transferred to index register Y. The value of the
base, 10, is then stored in the 16-bit word, bas, whichis located just before the 32-bit double
word, dnum, in memory. Index register X points to bas, and the contents of dnum is
initialized to zero as shownin Fig.4.7.
; 32 x 16 = 32 unsigned multiply
7; A:B x C = pH:pL A x C = ACH:ACL (drop ACH) B x C = BCH:
; pL= BCL PH = ALC + BCH
; Cc rmb z A rmb 2 B rmb 2
dumul:
psha ;save A
pshy ;save Y
ldd 0, x 7;D=C
ldy 2;x sY = A
emul ;Y = ACH, D = ACL
std “a; X z;save ACL
ldd 0,x 2D =C
ldy 4,x ;Y =B
emul 7Y = BCH, D = BCL
std 4,xX ;save pL = BCL
tts y,a 7D = BCH
addd 2,x ;D = BCHtACL = pH
stad 2,;% ;save pH
puly ;restore Y
pula srestore A
rts
62 Chapter 4
Example 13 — Calculator
As an example of using the conversion routine, number(ptr), we will write a C
program for a simple calculator as shown in Listing 4.6. Note in Listing 4.6, we define a
character array called kbuf that contains room for 12 characters. A 32-bit integer can have a
maximumof 10 decimal digits (largest unsigned value is 4,294,967,295). We must leave
space for a terminating invalid character and we might wantto include a negative sign, so we
will reserve 12 bytes for kbuf.
void main(void) {
long opl, op2; // 32-bit operands
char* ptr; // pointer to keypad buffer
char* blanks;
char kbuf[(12]; // keypad buffer
char ¢, a;
int Ww
ptr = kbuf;
blanks = " ws
Liquid Crystal Displays
Wedefine a pointer called ptr to this keypad buffer using the statement
char* ptr;
andthenset this pointer to the address ofthe first character in kouf‘using the statement
ptr = kbuf;
64 Chapter 4
After initializing the LCD and enabling the keypad weenter an infinite while loop.
Wefirst wait for a key on the keypad to be pressed (using getkey( ))and then return the value
inc. This hex value is converted to ASCII using hex2asc( ) and the result (in a) is stored at
the next location in kbuf. If the hex value c read from the keypad wasless than 10 (i.e. 0 —
9), then it is displayed on the LCD using data8(a) and the program waits for you to lift your
finger from the keypad. The kbuf index i is then incremented and you can continueto type
in additional digits that will be displayed on the LCD and the ASCII values stored in kbuf.
If you type a key other than 0 — 9 (i.e. A — F) then the e/se part of the if statement
will be executed. This contains the C switch statement, which behaves like a case statement
that executes one of several possible cases depending on the value of the switch expression,
in this case the value of the hex value c. Note that the last statement in each of the casesis a
break statement that terminates the switch statement.
If c is equal to OXE (1.e. you pressed the E or * key) then the first case is executed.
The first statement in this case 1s
which will convert the ASCII string that you typed from the keypad to the long integer op1.
Note that the ASCII value for E (0x45) was stored in kbuf when you pressed E and became
the invalid digit that wasn’t between 0 and 9. The next two statements will write this long
integer, right-justified in a field of 10, on the second line. The following two statementswill
clear the first line. After waiting for you to lift your finger, the program thenresets the kbuf
index i to zero, sets the LCD address to the beginning ofthe first line, and then executes a
break statement that will break out of the switch statement.
After typing in one number and entering it by pressing £, you can type in another
number, but instead of pressing E you should press A this time, which will add the two
numbers together and display the sum.
The second case in the switch statement will execute if you press the A key. It begins
by displaying op1 again on line two. Thefirst statement will convert the second number you
typed to binary and store the result in op2. Then the sum of op! and op2is stored back in
op| and displayed on the second line. Thefirst line is erased by overwriting it with blanks
and then the program waits for youto lift your finger.
The LCD address is reset to the beginning of the first line and you can now enter
another number. If you press A again, the old sum will be added to your third number, and
the new sum will be displayed on the second line. You can continue to add numbersin this
fashion. Pressing the F (or #) key will clear the display. Try it.
Note that when youcall /cd_init( ), the cursor is not displayed. This was useful when
we just want to display text or numbers as in Examples 11 and 12. However, in the case of
the calculator, it would be useful to display a cursor. You can do this by adding the C
statement instr8(OxOF), which will display a blinking cursor, or instr8(Ox0E), which will
display a non-blinking cursor (see Table 4.1). Try it.
Liquid Crystal Displays 65
PROBLEMS
4.1 Modify Listing 4.1 to display your namecentered onthefirst two rows.
4.2 Modify Listing 4.1 to display the digits 0 — 9 in a row using for loop.
4.3 Modify Listing 4.1 to display the following figure made of $ signs centered on the
display.
SSSSS$
$ $
4.4 Modify Listing 4.3 to display the 16-bit integer 123 in the center of the second rowof
the LCD display. Rememberthat write_int_Icd(int) will display leading blanksin a
field of 5.
4.5 Modify Listing 4.3 to display the 32-bit long integer 1234567 in the centerof the first
row of the LCD display. Rememberthat writelong_Icd(long) will display leading
blanks in a field of 10.
4.6 The algorithm shownin Fig. 4.5 will work for any base. If you want to display hex
values on the LCD youcan openthefile main.asm and change the statement
bas10: equ 10
to
basl10: equ 16
Then, if you re-compile and re-run the program showninListing 4.3 the value 54321
will be displayed as its hex equivalent D431 and the value 2345678123 will be
displayedas its hex equivalent 8BD0352B. Tryit. Don’t forget to change the value of
bas10 back to 10.
4.7 Modify Listing 4.6 to
a) display the result of subtracting the second numberfromthefirst number when you
press the B key.
b) display the result of multiplying two numbers whenyoupressthe C key.
c) display the result of dividing the second numberinto the first number when you
press the D key.
—-
-
yas=.
66 Chapter 5
Chapter 5
Interrupts
In this chapter we will introduce the idea of interrupts and show howto usethereal-
time interrupt to create a count variable that increments every 10 milliseconds. Wewill use
this count variable to blink a 7-segment display, implementa traffic light controller, and
blink the Morse code SOS.
5.1 HardwareInterrupts
1 You mustorder the DRAGONI12-Plus-USB with the Serial Monitor included; otherwise, by default,it
comes with a Debug12 monitorinstalled.
Interrupts 67
A hardware interrupt is an unexpected event that can occur at any time during the
ESaS oe aee ee
execution of a program. It might result from pressing a key, having a byte received in the
SCI port, or when sometimer has timed out. When a hardware interrupt occurs, a series of
events takes place. The current instruction is completed, and then the programmingregisters
(see Fig. B.1 in Appendix B) are pushed on the stack. The return address will be the valuein
the program counter; i.e., the address of the instruction following the one being executed
when the interrupt occurs. This will be the address returned to after the interrupt service
routine is executed. After all registers in Fig. B.1 are pushed on the stack, both the / bit and
the X bit in the condition code register CCR are set This meansthat anotherinterrupt cannot
get serviced during the execution of the interrupt service routine. The address of the
interrupt service routine is loaded from the interrupt vector table into the programcounter so
that the first instruction in the interrupt service routine will be executed. The last instruction
of an interrupt service routine is the R7/ instruction, which will pop the registers shown in
Fig. B.1 off the stack, including the CCR register which will have its / bit cleared. At that
point a new interrupt can occurincluding one that might have occurred during the processing
of the previousinterrupt.
A list of all interrupt sources available on the MC9S12DG256 is given in Table D.1
in Appendix D. Note that each interrupt source has a vector number (between 0 and 57)
associated with it. The address of the interrupt vector for each interrupt source is also shown
in Table D.1.
The word interrupt tells the C compiler that this is an interrupt service routine and the
number 7 is the interrupt vector number from Table D.1. In this case the 7 is the vector
numberfor a real-time interrupt with the interrupt vector stored in addresses SFFFO - SFFF1.
The name ofthe interrupt service routine is handler( ), The compiler will automatically
assign the address of this interrupt routine to the proper interrupt vector address. When
using the Serial Monitor and CodeWarrior, these interrupt vector addresses have been
mapped to $F780 - $F7FF. Thus, you will find the address ofthe real-time interrupt service
routine handler( ) at addresses $F7FO - SF7F1.
We havewritten three assembly language routines for real-time interrupts that can be
called using the C functions shownin Table 5.1. The routine R7/ init() enables a real-time
interrupt that produces an interrupt every 10.24 milliseconds. Whenaninterrupt occurs, it
sets a flag in one of the RTI registers. You must clear this flag in the interrupt service
routine so as not to cause another interrupt immediately upon leaving the interrupt service
68 Chapter 5
routine. The C function clear_RTI_flag( ) will do this. The C function R7/_disable( ) will
disable real-time interrupts. We will give three examples of using the real-time interrupt.
which just increments the variable ticks every 10.24 ms and then clears the RTI flag.
Note in Listing 5.1 we have defined the function half_second_delay( ) thatfirst reads
the current value ofticks (that is being incremented every 10.24 msby thereal-time interrupt
routine) and stores this value in ficksO. It then stays in the while loop as long as (ticks —
ticksO) is less than 49. Thus, the w/i/e loop will exit after 49 interrupts which will take 49 x
10.24 ms = 0.502 seconds. Rememberthat ticks is being incremented in the background by
the real-time interrupt routine. Once you enable this real-time interrupt by calling RT/_init(
), the value of ticks gets incremented every 10.24 ms. The main programin Listing 5.1
blinks the digit 8 on the 7-segment display on and off every second.
The registers associated with real-time interrupts are shown in Fig. 5.1. The R7/CTR
register determines the timeout period. In the formula for the timeout period in Fig. 5.1, the
three bits RTR[6:4] can have values from | — 7, and the four bits R7R[3:0] can have values
from 0 — 15. This means that the numerator in the fimeout_period formula can range from
1x2!° to 16x2'®. The value of OSCCLK in the formula is the oscillator, or crystal, clock
frequency, which is 8 MHz on the DRAGON12-Plus-USB. To set the timeout period to
10.24 ms, you would store the hex value $54 in R77CTR on the DRAGON 12-Plus-USB.
The following is the calculation.
To enable real-time interrupts, you would write a 1 to bit 7 (R77E) of the CRGINT
register. The assembly language instruction
Interrupts 69
will do this. To disable real-time interrupts, you would write a 0 to bit 7 (RT/E) of the
CRGINT register. The assembly language instruction to do thisis
bclr CRGINT, #580
When areal-time interrupt times out, bit 7 (RT/F) in the CRGFLGregister gets set to
1. If real-time interrupts have been enabled bysetting bit 7 in the CRGINTregister, then a
real-time interrupt will occur. To clear the R7/F bit, you must write a | to this bit. This
may seem strange, but this is the way that most flags are cleared. Writing a 0 to a flag bit
has no effect. Therefore, to clear the flag in bit 7 of the CRGFLGregister, you could store
the hex value $80 in this register using the statement
movb #$80, CRGFLG
Note that all of the zeros that are written to the other bits in the CRGFLGregister will have
no effect on the flags that may beset in these otherbits.
void main(void) {
PLLinit (); f/ set system clock frequency to 24 M
seg? enable(); // enable ?-segment displays
leddisable(); // disable LEDs
RTI init();
while(l) {
seg/dec (8, 3); ‘f display 8 on 7seg display #4
half sec delay();
seg’s of f(); f/f turn off 7seg display
half sec delay();
}
}
Listing 5.2 shows the assembly language subroutines that correspond to the three C
function calls shown in Table 5.1. Note that the first instruction, se/, in the subroutine
RTI_init disables hardware interrupts by setting the I-bit in the condition code register (CCR)
to 1. Hardware interrupts are then enabled at the end of this subroutine by clearing this I-bit
in the CCR using the instruction c/i. It is always a safe procedure to disable interrupts while
setting up a hardware interrupt, and then enable hardwareinterrupts using c/i after all setup
has occurred.
.
, clearRTI flag();
clear RTI_ flag:
movb #$80, CRGFLG ;clear rti flag
rts
disable RTI
RTI disable():
RTI_ disable:
bclr CRGINT, #$80 idisable rti
rts
Interrupts 71
MyCode: SECTION
asm_main:
rti_intser:
ldd ticks
subd #1
std ticks ;decrement ticks
bne CEL ;if ticks = 0
com PORTB ; toggle display
movw #ticks max,ticks ; reset ticks to 49
rti: movb #$80,CRGFLG ;clear rtif
rts
blinkrti:
sei ;disable interrupts
movb #SFF,DDRB ;port B outputs
movb #SFF,DDRP ;port P outputs
movb #SFF,DDRJ ;port J outputs
movw #1,ticks jticks = 1
bset PTJ,$02 ;disable leds
movb #$07,PTP ;enable digit 3
elr PORTB ;turn 7seg off
movb #$54,RTICTL set rti to 10.24 ms
bset CRGINT, #$80 ;enable rti
eke ;enable interrupts
rts
end
void main(void) {
PLLinit(); // set system clock frequency to 24 MHz
blink_rti ()? // blink 7seg display using rti
while(1) {} /* wait forever */
Interrupts 73
To simulate these traffic lights you could plug colored LEDsinto the protoboard and
connect them to Port M as shownin Fig. 5.3. Whenthe pin output is lowthe output of the
inverter is high (+5 volts) and no current can flow through the LED andtheretore nolight
will be emitted. If you bring the port output pin high, the output of the inverter goes low
(assume about 0.2 volts) and current will flow from the +5 volt power supply through the
resistor R and the LED. Theresistoris used to limit the amount of current that flows through
the LED. A typical current would be 15 milliamps or 15 x 10-5 amps. Using Ohm's law we
can compute theresistor size needed as shown in Fig. 5.3. You could, for example, connect
the east-west lights to PMO-PM2and the north-southlights to PM3-—PM5.
Wecan usereal-time interrupts (see Example I4) to continually cycle through the six
states shown in Table 5.2. Note that whenthe light on onestreet is red and the light on the
other street is green, we will delay 5 seconds. (Assume veryfast cars so that you won't have
to wait all day!) We will delay | second on a yellow-red or red-red combination.
No current no light ey
+
+5V WV > o<] 5] PMS
R LED 7406
light
Current
——_
R LED 7406
_ voltage ‘ _ aw — 1.7
a = 220 ohms
current 15x 10°
g a 1 in PM5
Figure 5.3 Turning on an LED by storin
74 Chapter 5
The idea is to use interrupts so that the entire operation will be carried out in the
background with no need for the CPU to intervene to keep thetraffic lights going. The same
idea can be used to cycle through any set of states, which you can change by writing to an
output port.
Webegin by defining two arrays that represent the six states shown in Table 5.2.
These arrays are called /ights[ ] and delay[ ] in the program shownin Listing 5.5. The first
byte in the array /ights[ ] is the hex value 0x0C. This is the binary value 00001100 which
will be written out to Port M. The bits in Port M will be assigned to the “colored” LEDs
according to the bit positions --RYGRYG. Thus the hex value 0x0C will turn on the green
north-south light and the red east-westlight.
Thefirst entry in the array delay[ ] is the delay time measuredin ticks. A tick will be
the time between interrupts, which is 10.24 ms. Therefore, a delay of1 second will be 488
ticks and a delay of 5 seconds will be 98 ticks. The rest of the entries in the /ights[ ] array
store the values to be written to Port M for each state, and the corresponding entries in the
delay[ ] array contain the delay timeforthat state.
The variable dtime defined at the beginning of the program in Listing 5.5 1s used to
hold the numberofticks before a timeout that will moveto the next state. This value will be
initialized to 1 so that a state change will occur on thefirst interrupt. The variable ix will be
the index into the states.
The interrupt service routine, handler(), shown in Listing 5.5 starts by decrementing
the value of dtime. If the decremented value is not zero then the /F statementis skipped and
the RTIF flag is cleared. If the decremented value of dtime is equal to zero in the interrupt
service routine, then the /F statement is executed. Thefirst statement turns on the nextset of
lights by writing the value of /ights[ix] to Port M. The next statement stores the
corresponding delay time for that state in dtime. The state index ix is then incremented and
whenit equals the numberofstates (6)it 1s reset to zero.
In the main program, Port M is set as output, the real-time interrupts are enabled,ix is
set to 0, and dtime is set to 1. The main program then just enters an infinite while loop. The
interrupt routine takes care of changing the traffic lights on schedule. The main program
could go on and do useful things while the traffic light is changing all on its own!
Listing 5.6 is a version ofthis traffic lights program that will run directly on the
DRAGON 12-Plus-USB using its red LEDsasthe traffic lights. You will have to pretend
that some of them are yellow and green!
Interrupts 75
eer. ~~ eo
// delay time
int dtime;
// index into states
int ix;
const int numstates = 6; ;
const char lights[] = { // --RYGRYG
0x0C, // 00001100
0x14, // 00010100
0x24, // 00100100
0x21, // 00100001
0x22, // 00100010
// 00100100
—
Ox24,
——— . ee
};
const int delay[] = {
488, // 5 sec delay
98, // 1 sec delay
// 1 sec delay
Vaee
98,
488, // 5 sec delay
98, // 1 sec delay
98, // 1 sec delay
be
$2 Ww 62, fp
include <hidef. h> /* common defines and macros */
nclude <mc9sl2d0256.h> /* derivative information */
hs
“Qe
p+
taf,
¢
S
INFO DERIVATIVE "mc9sl2dg256b"
tt
PR:
-s
“
ww
a
"tj
it
i
we
wo
QO
r
void main(void) {
PLL_init(); // set system clock frequency to 24 MHz
led enable(); // enable LEDs
seg? disable(); // disable 7-segment displays
RTI_init(); // initialize RTI to 10.24 ms interrupts
ix = 0; // reset index into states
dtime = 1; // start traffic light right away
while(1) { // do nothing while traffic light goes
}
three times quickly to indicate another dot-dot-dot. This sequence will then be repeated
endlessly.
A program for doing this is shown in Listing 5.7. Note that it follows the same
pattern that we used for the traffic light example in Listings 5.5 and 5.6. In this case, there
are a total of 18 states: 6 to turn the S on andoffthree times; 6 to turn the O on and off three
times; and 6 to turn the S on andoff three times again. The on and off delay time for the dot
is 12 x 10.24 ms = 0.123 seconds andthe on and offdelaytime for the dash is 36 x 10.24 ms
= 0.369 seconds. A slightly longer delay is usedat the end of thefirst three dots and an even
longer delay is used at the end of the entire SOS sequence. Try this program.
Chapter 6
Analog-to-Digital Converter
In this chapter we will show howto use the two 8-channel analog-to-digital (A/D)
converters that are part of the MC9S12DG256 microcontroller. We will use the A/D
converter to read the potentiometer value, read the values of an x-y-z accelerometer, and
measure the temperature in Fahrenheit and Centigrade.
A/D converters transform an analog voltage within a given voltage range into a
corresponding digital number. For example, you might convert a voltage between 0 and 5
volts to an 8-bit binary number between 00000000 and 11111111. This represents a decimal
number between 0 and 255. In this case, a change in the least-significant bit (LSB) of 1
corresponds to a change in voltage of 5V/256 = 19.5 mV. This quantization error, or step
size, is inherent in any type of A/D conversion. We can minimize this error by using more
bits. For example, a 10-bit A/D converter will have a step size between 0 and 5 volts of
5V/210 = 5V/1024 = 4.9 mV. The MC9S12DG256 A/D converters can be programmed to
do either 8-bit or 10-bit conversions. We will provide C functions to do 10-bit A/D
conversions.
There are several different methods used for performing A/D conversions. One of
the most popular, and the one used in the HCS12 microcontrollers, is the method of
successive approximation. We will illustrate this method by using a 4-bit conversion in
which the step size between 0 and 5 volts will be 5V/24 = 5V/16 = 0.3125 V. For x bits the
method of successive approximation requires n steps. The methodis essentially a binary
search as shownin Fig. 6.1 for fourbits.
Suppose that the input analog voltage to convert is Vj, = 3.5V. The first step 1s to
guess the mid-range voltage of 2.5V corresponding to the binary number 1000. That is, we
just set the most-significant bit. If this voltage (2.5V) is less than the voltage Vj, then we
want to keep this bit, because we know the input voltage is greater than 2.5V. We then add
the next most-significant bit and try 1100, or 3.75V, in step 2. This voltage is greater than
3.5V, so we have overshot the mark and mustdiscard this bit. Setting the next bit meansthat
we will try the value 1010, or 3.125V is step 3. This value is less than 3.5V so we will keep
this bit. Finally, we will set the last (least-significant) bit and try 1011, or 3.4375V in step 4.
This is still less than 3.5V so we keep this bit. We are now done and ourconverted value is
1011, which really represents 3.4375V, but is within our error margin of 0.3125V.
Analog-to-Digital Converter 79
voltage
SV 11
1110
1101
1100 3.75V
1011 3 4375V Vin = 3.5V
3.125V
2.5V
0101
0100 |
0011 |
0010
0007 | |
OV
0000
step 1 step 2 step 3 step 4
DA
D/A Converter
The MC9S12DG256 microcontroller has two 8-channel A/D converters, called A 7D0
and 47D1, which can produce either 8-bit or 10-bit conversions. These A/D converters
share the same input pins as PORTADO and PORTADI1, whichcan be usedfor digital inputs
When the AD converter is not being used. Pin 7 of PORTADOis connected to the center tap
of the potentiometer on the DRAGON 12-Plus-USB board. This input will vary between 0
and 5 volts as the white cap of the potentiometer (pot) is turned with a small screw driver.
We will use this input to illustrate the use of ATDO.
There are lots of registers associated with the use of the A/D converters. We will
look at these in the Under the Hood section below. To makeit easy, we have written several
assemblylanguage routines that are called by the C functions shown in Table 6.1.
The pinouts of the two A/D converters are shown in Table 6.2. On the DRAGON 12-
Plus-USB,. channel 7 of 47D0 is connected to the potentiometer, channel 5 of ATDO is
connected to a temperature sensor, and channel 4 of A7D0 is connected to the output of a
phototransistor. The DRAGON12-Plus-USB has a 10-pin header that is connected to
Channels 0-2 ofATD1. This header is convenient for connecting an x-y-z accelerometeror
the GP2D12 distance sensor to A/D channels of the MC9S12DG256 microcontroller.
int val;
void main(void) {
PLL:init () 7 // set system clock freque
ad0_enable(); // enable a/d rere
ledinit (); // enable led
while(1) {
val = adOconv(7); // read pot on channe
val = val >> 1; // shift 1 bit right:
set_lcd_addr (0x40) ; // display on 2nd row
writeint lcd(val); // write value
msdelay(100); // delay 0.1!
}
)
;
—=
82 Chapter 6
ava40:
pshx ;Save reg
ldx #ATDODROH
ldd 2,X%+ ;adar0d
addd 2,x+ j;tadrl
addd 2,x+ ;+adr2
addd 2,x+ ;t+adr3
lsrd
1lsrd ;divide by 4
pulx ;restore reg
rts
SEAN =0 MULT=0
orab #$80
stab ATDICTLS iright j t
ane jus
adll brclr ATDISTATO, #$80, ad11 ;wait for conv
bsr avg4l
res
avg4 is
pshx .
ldx #ATD1DROH ene! Seg
ldd 2,%+ —
addd 2,x+ - r0
addd 2,x+ ome
addd 2,x+ pmade2
lsrd ‘tadr3
lsrd
pulx divide by 4
rts ‘Yestore reg
ie
Analog-to-Digital Converter 83
Once the A/D converter module has been enabled, a conversion is started by writing to
control register ATDOCTLS. Setting bit 7 (DJM) to 1 will right justify the data in the result
registers. Storing a 0 in bit 5 (SCAN) and bit 4 (MULT) will cause a single conversion
sequence ofonly one channel. The channel numberis stored in bits 2— 0. In the subroutine
ad0convinListing 6.2, the channel numberis passedas a parameter in accumulator B. After
masking this numberto the lower three bits, bit 7 is set to 1 to right justify the data in the
result registers, and the resulting value is written to ATDOCTLS. At this point, the
conversion begins. The numberof conversions that take place is determined bythe value of
the conversion sequence length in ATD Control Register 3 shownin Fig. 6.3. On reset, the
default value of this conversion sequence length is 4, which means, in our case, that four
separate conversions of the analog signal on the selected channel are performed, and the
results are stored in the first four words of the ATD Conversion Result Registers
(ATDDRHx/ATDDRLYX).
84 Chapter 6
Whenthe conversion is complete, bit 7 (SCF) of the ATD Status Register 0, shown in
Fig. 6.4, is set to 1. The branch onclear statement
in the subroutine ad0conv in Listing 6.2 will branch on itself as long as the AND of
ATDOSTATO with $80is zero, 1.e., as long as bit 7 of ATDOSTATOis 0. As soon as this SCF
flag goes to 1, the branch will fail, and the next instruction, bsr avg40, will be executed.
The subroutine, avg40, will average the four values that have been storedin the first
four ATD Conversion Result Registers shown in Fig. 6.5. Note that the 10-bit conversion
result is right justified in the two bytes ATDODRxH/ATDODRxL. In the subroutine, avg40,
index register X points to ATDODROH. The first conversion result in
ATDODROH/ATDODROL is loaded into accumulator D, and then each of the next three
conversion results are added to this value. The result is then divided by 4 (by shifting 2 bits
to the right) to produce the average value in D. This is the result that is returned in the C
function int adconv(char ch#).
Listing 6.3 shows a program that will allow youto test the accelerometer module by
continuously displaying the x-, y-, and z-components of acceleration on thefirst rowof the
LCD. As you tilt the accelerometer module these values will change because you are
measuring the acceleration of gravity. When the module is horizontal the z-value will be
maximum (corresponding to 1 g) and will become minimum if you turn the module over
—————————
(corresponding to -1 g). When the moduleis horizontal the x- and y-values are reading an
acceleration value corresponding to 0 g. These values will increase or decrease as youtilt
the module in one direction or another.
You should run this program andtilt the accelerometer until you understand whatIt is
i
measuring. Try shaking the module to see what maximum (and minimum) accelerations you
can detect.
int ax;
int ay;
int az;
void main(void) {
PLL init (); // set system clock frequency to 24 MHz
adl enable(); // enable a/d converter 1
ledinit(); // enable lcd
while(l) {
set lcd addr(0x00); // display on lst row of LCD
ax = adlconv(0); // read ax on channel 0
write int lcd(ax); // write value in field of 5
ay = adlconv (1); // read ax on channel 1
write int lcd(ay); // write value in field of 5
// read ax on channel 2 ;
az = adlconv (2);
write int lcd(az); // write value in field of 5
ms delay(100); // delay 0.1 seconds
86 Chapter 6
a, = gsin@ (6.3)
and
a. = gcos0 (6.4)
ua =tand=— (6.5)
a_
To calculate the values of a, and a- to use in Eq. (6.5) we must subtract the value
corresponding to zero gravity from the measured accelerometer values. Let ao be the
measured accelerometer reading corresponding to zero gravity. We will assume that this
value is the same value for both a, and a-:. Thatis, it is the a, value when @ =0, andis the a:
value when @=90°. We will measure ao by measuring a, when @=0. In orderto deal only
with integer values we multiply Eq. (6.5) by 1000 before doing the calculation. Thus, the
integer that we compute will be calculated from
ut, = 1000 a. 7 ay
(6.6)
a, =,
Listing 6.4 will performthis calculation. To make the measurement you would make
sure that the poster board was horizontal and then press the reset button. This will start the
program and calculate a0 by averaging eight readings of ax. Then lift the poster board
slowly. The value of tang times 1000 will continuously be displayed on the LCD. This
displayed value whenthe block just starts to slide down the poster board will be 1000 times
the coefficient of static friction.
Analog-to-Digital Converter 87
Where 7). is the temperature in degrees Centigrade. This sensor is mounted on the
DRAGON 12-Plus-USB board and the output analog voltage is connected to channel 5 of
ATDO.
We can measure the temperature by reading the analog signal on channel 5 of ATDO
on the DRAGONI2-Plus-USB. A reading of 1023 will correspond to 5 volts. We can
convert I:q. (6.7) to A/D value readings by noting that 10 mV correspondsto areading of2.
Therefore, in terms of the A/D reading, val, Eq. (6.7) can be written as val = 27., from which
we can approximate the temperature in Centigrade as
void main(void) {
int: val, Te, ‘TL:
char* ql;
char* q2;
ql = " deg C";
q2 = " deg F";
PLL init (); // set system clock frequency to 24 MHz
ad0 enable (); // enable a/d converter 0
led_init(); // enable lcd
while(l) {
val = adOconv (5); // read temp sensor on channel 5
val = val >> 1; // shift 1 bit right (divide by 2)
Tc = val; // degrees C
T£ = Tco*9/5 + 32; // degrees F
set_lcd_addr (0x00); // display on lst row of LCD
writeint_lced(Tc); // write value in field of 5
type lcd(ql); // write deg C
set_lced_addr (0x40); // display on 2nd row of LCD
write int lcd(Tf£); // write value in field of 5
type lcd(q2); // write deg F
ms delay(100); // delay 0.1 seconds
}
Pulse-Width Modulation (PWM): Motors and Servos 89
Chapter 7
In this chapter we will show howto control the speed anddirection of a DC motor
andthe position of a servo. The DRAGON12-Plus-USB board has convenient headers and
terminals for connecting motors and servos.
When connecting a motor or some other load that may r J18 , : S25,
The spend of a DC motor depends on the voltage applied to the motor — the higher
the voltage the faster the motor will turn. If you just want to turn on a motorat a constant
speed, you can connect one side of the motor to AZOT/ in Fig. 7.1 and connect the other side
of the motor to ground. In this way you could connect up to four motors to the SN754410 in
Fig. 7.1. The polamty of the voltage connected to the motor will determine which waythe
motor tums. [fits tuming the wrong way, just exchange the two connectionsto the motor.
pwm
<@ duty —>
<——__—__—__—_———_ pe riod >
e average DC value ofthe pwsignal in Fig. 7.2 will be proportional to the duty
cycle. A duty cycle of 100%will have a DC value equal to the maximum value of the pwm
signal. A duty cycle of 50%will have a DC value equal to half of the maximumvalue ofthe
pwmsignal, and so forth. If the voltage across the motor is proportional to this pwm signal,
then simply changing the pulse width dury andtherefore the duty cycle changes the speed of
the motor.
Port P of the MC9S12DG256can be usedto generate upto eight 8-bit PWMsignals
or four 16-bit PWMsignals. An 8-bit PWM signal will have a resolution of 256 different
values of the pulse width duty. A 16-bit PWMsignal will have a resolution of 65,536
different values of the pulse width duty. For speed control of a DC motor an 8-bit PWM
signal is usually more than adequate. We provide assembly language routines for generating
up to eight different 8-bit PWMsignals. For controlling a servo, a 16-bit PWM signal ts
sometimes needed, and we will provide two such routines in the neat section,
The $N754410 quadruple half-H driver shown in Fig. 7.1 has intemal diodes from
the output to .,, and to ground that will eliminate possible voltage spikes that would tend to
showup whenthe current through the motor changes quickly.
The built-in C functions that can be used to control the speed of a DC motor are
shownin Table 7.1.
Pulse-Width Modulation (P/M): Motors and Servos ot
f a OC motor
Table 7.1 C function calls for controlling the speedo
C Function Call Meaning
motord init ()3 Initialize PWMO with TO'msperod
motorl init)? Initialize PWM with $0 ms panos
motor? init ()? Imtiahze PWM2 with 10 mrs perod | ___]
motors Lait t?? Initiahze PWM3with 1Omspencd
motord init(): Initahze PWM4 with TO ms penod
motors init? Initialize PWM5 with 10 ms pened
motoré init); Initiahze PWM6 with 10 ms period
Initialize PWM7 wath 10 ms penod 3
moter? inttQ> ~ ie eR sr if IRS
motorO(int speed); Set speed of motor | £2)
motori (int speed) s —__Set speedof motor! (0(0 255) _
in
The registers used for programming the PWM module are stiown tp bigs. 7 Ja and
7.3b. Each ofthe eight outputs of Port P (PTP) can be cnabled as a PWM output using the
PWMEregister in Fig. 7.3a. The me at which the output goes Hash and low is controlled by
the 8-bit counter, PHMCNTx, shownin Fig, 7.3b, The clock source for this counter can be
clock A, B, SA, or SB, depending on the settings in register PWMICLA bor channels 0 and
L, which are used in the motor driver in Piz. 7.1, we have sleeted clock San the
subroutines motorO_ init and motor! init in Listing 72 The clock trequenes is determined
by clock scaling and prescaling registers, PH MSCLA, PU MSCES, aod PH MERCER aw | To
7.3b. In Listing 7.2, we have set the prescalar clock, PIEMPRCLA, te hex 822, which means
~~.
that the clock rate ts the bus clock (24 MHz) divided by 4, oro Mts The seale of register
Is set to hex $75 = 117, which means that the Sa clock nate is @ MELe (28117) = 25.64 kHz.
If we set the period register, PIPER, in Big 7 ib tw 283, the period will be
255/25.04 kHz = 9.95 ts.
92 Chapter 7
int val;
int speed;
void main(void) {
PLL init(); // set system clock frequency to 24 MHz
ad0Q_enable(); // enable a/d converter 0
seg7_ disable(); // disable 7-segment displays
led_enable(); // enable leds
led init(); // enable lcd
SW_enable(); // enable switches
motor0O init(); // enable 8-bit pwm0 for motor
while(1) {
leds _on(SW1_dip()); // display dip switch settings on leds
val = adOconv(7); // read pot on channel 7; 0 - 1023
speed = val >> 2; // shift 2 bit rights 0 = 255
motor0 (speed) ; // set motor speed
set_lcd_addr (0x40); // display on 2nd row of LCD
writeint_lcd(speed) ; // write value in field of 5
ms delay(100); // delay 0.1 seconds
The polarity register, PWMPOL,in Fig. 7.3a will determine if the PWMsignalstarts
high and goeslow,or starts low and goes high. In Listing 7.2, we set the channelbits to | in
this register, so in our case, the PWM signal will start high. By clearing the center align
enable register, PWMCAE,in Listing 7.2, the PWMsignal will be left aligned. Two PWM
channels can be concatenated to form a 16-bit PWMchannelbysetting the appropriate bits
in the PWM control register, PWMCTL,in Fig. 7.3b. For the motors, we are using the two
8-bit channels 0 and 1, so we clear bit 4 in PWMCTLin Listing 7.2.
The value stored in the channel duty register, PVMDTYx, in Fig. 7.3b will determine
when the PWMsignal will go low. By storing a value of 128 in this register in Listing 7.2,
the initial duty cycle will be 50%. The last bsef instruction in the motor initialization
subroutine will set the appropriate bit in the PWMenable register, PWA/E, in Fig. 7.3a.
Once the PWM channelis enabled, the PWMsignal will appear on the corresponding pin of
Port P (PTP). The subroutines, motor0_off and motor!_off, in Listing 7.2 will stop the
motors by disabling the PWMsignal.
The speed of the motor is changed by changingthe value in the channel dutyregister,
PWMDTYx. The subroutines motor0 and motor! in Listing 7.2 do this by writing the value
passed in accumulator B to the channel duty register, PIVMDTYx.
A servo motoris a special type of device that contains a DC motor, some gears, a
potentiometer, and electroniccircuitry for position feedback control, all packaged in a single
compact device. These servos are widely used in
model airplanes and radio controlled cars and are
therefore mass produced and very inexpensive. A
typical servo ofthis type, the Futaba $3004, is shown
in Fig. 7.4. This servo has three wires attachedtoit:
the red wire goes to +5 volts, the black wire goes to
ground, and the white wire goes to a PWM signalthat
controls the position of the motorshaft.
The motor shaft is prevented from moving
more than about 0 degrees by limit stops. The PWM
signal used to control the position of a servo is shown |
in Fig. 7.5. Note that the period is fixed at 20 ms and
the pulse width varies from about L.1 ms to L.9 ms in
order to movethe shaft position throughatotal angle Kieure 7.4
of about 90 degrees. The Futaba $3004 servo
Inasmuch as the useful duty cycle of this
PWM signal varies from only about 5 to 10 percent, we will use the 16-bit option with a 3
MHz PWMclock in order to provide a resolution of 60,000 over the 20 ms period. In this
case, the value of duty in Fig. 7.2 would be about 4500for the neutral position, 3300 for the
+45 degree position, and 5700for the -45 degree position.
The built-in C functions that can be used to control the position of a servo are shown
in Table 7.2.
96 Chapter 7
{— period = 20 ms —P
~¢ period = 20 ms >
<q period = 20 ms oe
A sample programthat uses these functions is shownin Listing 7.3. In this example
the pot on the board controls the position of a servo.
A second sample program that continually rotates the servo back and forth through an
angle of about 90 degrees is shown in Listing 7.4. You can experiment with this program by
changing the for loop values and the delay time.
Pulse-Width Modulation (PWM): Motors and Servos 97
void main(void) {
int val;
int width;
void main(void) {
int width;
PLLinit (); // set system clock frequency to 24 MHz
servo76init(); // enable pwml for servo
while(1l) {
for(width = 4500; width <= 6000; width = width + 5) {
set_servo76 (width) ; // move servo from 4500 to 6000
ms delay(5);
}
for(width = 6000; width >= 3000; width = width - 5) {
set_servo76 (width) ; // move servo from 6000 to 3000
ms_delay(5);
}
for(width = 3000; width < 4500; width = width + 5) {
set_servo/76 (width); // move servo from 3000 to 4500
ms delay(5);
}
98 Chapter 7
Listing 7.5 shows the assembly language routines corresponding to the four C
function calls in Table 7.2. In the subroutine, servo54_init, we concatenate channels 4 and 5
by writing a | to bit 6 in the PWM control register, PWWMCTL, in Fig. 7.3b. Note that the
channel 5 pin will be used for the output, and the channel 5 bits are used in the PWMPOL,
PWMCLK, PWMCAE, and PWMEregisters. We select clock A by clearing bit 5 in
PWMCLK. By storing a hex $33 in PWMPRCLK, weset the clock frequency to 3 MHz by
dividing the 24 MHz bus clock by 8. To make the period exactly 20 ms, we store a value of
60000 in PWMPER4 (which is the concatenation of channels 4 and 5), which will give a
period of 60000/3 MHz = 20.0 ms. Storing 4500 in PWMDTY4will give aninitial pulse
width of 1.5 ms.
The subroutine set_servo54 will store the value passed in accumulator D in register
PWMDTY4, which will be the concatenation of PWMDTY4 and PWMDTYS5, with
PWMDTY4 being the high byte. To move the servo over its complete range, this value
stored in PIVWMDTY4 should range from about 3000 to 6000.
The subroutines servo76_init and set_servo76 in Listing 7.5 behave in a similar
manner to servos4init and set_servo54 except in this case channels 6 and 7 are being
concatenated. The channel 7 pin is used for the output, and the channel7 bits are used in the
PWMPOL, PWMCLK, PWMCAE, and PWMEregisters. The 16-bit period and duty values
are stored in PIVMPER6 and PWMDTY6,respectively.
Pulse-Width Modulation (PWM): Motors and Servos 99
Chapter 8
In this chapter, we will show how to communicate from a serial port on your PC to
an SCI port on the DRAGON12-Plus-USB board. We will provide new C function calls to
make it easy to do this. The DRAGON 12-Plus-USB has a USB portthat connects directly to
a USB port on your computer. However, it will be listed as a serial port under Ports in the
Components section of the System Summary on your computer. Youwill need to determine
from this Ports section what COM port number to use in CodeWarrior to connect to the
DRAGON12-Plus-USB board. If you are using an older DRAGON 12-Plus board that has a
standard 9-pin serial connector, you will need to use a USB-to-serial converter cable to
connect a USB port on your computer to the serial connector on the DRAGON12-Plus
board.
There are two basic types of serial communication: synchronous and asynchronous.
In synchronous communication, the timing is controlled by a standard clock at both the
transmitter and receiver ends, and data are normally sent in blocks that often contain error
checking. On the other hand, the timing for asynchronous communication is handled one
character at a time and while the clocks at the transmitter and receiver must be
approximately the same, they are resynchronized with each character. Because each
character requires these additional synchronizing bits, asynchronous communication is
slower than synchronous communication. However, it is simpler to implement andis in
widespreaduse.
Asynchronousserial communication uses a start bit to tell when a particular character
is being sent. This is illustrated in Fig. 8.1, which showsthe transmitted waveform whenthe
character "7" (ASCII code = 0x54) is sent with odd parity. Before a character is sent, the
line is in the high, or mark state. The line is then brought low (called a space) and held low
for a time r called the bit time. This first space is called the start bit. It is typically followed
by seven or eight data bits. The least significant bit DO is transmitted first. For example, in
Fig. 8.1 the seven bits corresponding to the ASCII code 0x54 (the character "7") are sent
starting with DO. These seven bits are followed by a parity bit. This bit is set to a 1 or a0
such that the sum of the numberof 1's transmitted is cither even or odd. We have used odd
parity in Fig. 8.1. Since three 1's were sent (D2, D4, and D6)the parity bit is zero, Often a
character is sent with no parity and 8 data bits. The parity bit is followed by one or two stop
bits, which are always high (a mark). The next character will be indicated by the presence of
the nextstart bit.
Serial Communication Interface (SCI) 101
The reciprocal of the bit time is called the baud rate. Some common baudrates used
in serial communication are given in Table 8.1. We will provide you with a C functioncall
that allows you to set any baud rate when youinitialize the SCI port.
DO D1 D2 D3 D4 D5 D6 D7 STOP
MARK ————-
SPACE |__|
START PARITY
Figure 8.1 ASCII code 54H = 1010100 ("T") sent with odd parity
The MC9S12DG256 has two separate SCI modules, SCIO and SCII. On the
DRAGON 12-Plus-USB board, jumper J42 is used to select whether SCIO or SCII ts
connected to the USB connector that you use to connect to a USB port of your PC. SCIO is
normally selected. Both SC1O and SCII have TTL header connectors on the DRAGON12-
Plus-USB board.
A functional block diagram of the serial communication interface is shownin Fig.
8.2. The main function of the SCL is to transform parallel data from the HCS12 intoserial
data and send it out through the transmit data pin 7xD, andto receive serial data through the
receive data pin RxDandtransform it to parallel data that can be read by the HCS 12.
The signals 7xD (pins PSI or PS3) and RxD (pins PSO or PS2) are generally
connected to a 9-pin "D" connector through an ELA-232-D (formally RS-232C)
driver/receiver chip. This driver/receiver transforms the logic 0 (0 volts) and logic | (5
Volts) signals to +12 volts and —12 volts respectively. (Sometimes +3 volts and —5S volts are
used.) This allows for more noise immunity when sending the signals over a long distance.
Pin 2 on the "D" connector ts normally the "transmit" pin 7D on the microcontroller board
and pin 3 is the "receive" pin RxD, But if a straight-through cable is connected from the
Serial port on a PCto yourtarget HCS 12 board, then the PC's "transmit" pin must be pin 3 so
102 Chapter &
that it is connected to the RxD pin on the HCS12 board. Similarly, pin 2 on the PC will be
the “receive” pin sothat it is connected to the 7xD pin on the HCS12 board.
If you want to communicate between two DRAGONI2-Plus-USB boards using the
SCI port, it is necessary to interchange the wires in the cable so that the transmit pin at one
end is connected to the receive pin at the other end, and vice versa. We call this a null
modem,
D8! D7 D6 DS D4 D3 D2 D1 DO
Receive Shift Register .
Serial Data In =
Listing 8.1 is a program that initializes the SCIO port to 9600 baud, waits for a
character to come in RxD, and then sends the same character back out 7xD. Totest this
program download it to the DRAGON12-Plus-USB board in the usual way, execute the
program, and then makesure that you close the Real-Time Debugger window. Then run any
convenient terminal program, such as HyperTerminal, running at 9600 baud. You can find
HyperTerminal on your PC by going to Start -> Programs -> Accessories ->
Communications -> HyperTerminal. Make sure you set up HyperTerminal with no hardware
handshaking. Pressing any key on the PC keyboardwill send the ASCII code ofthe key out
the serial port to the DRAGON 12-Plus-USB, which will display the character on the LCD
and then echo it back to the PC, where it will be displayed. Note that the terminal program
does not display a character on the screen until it has made a roundtrip to the DRAGON 12-
Plus-USB board and back. Try it. Note that when youget to the end of thefirst line ofthe
LCD display you must type 48 more characters before a character gets displayed on the
second line. (Recall the LCD memory addressesin Fig. 4.3.)
Listing8.1 Example 23
// Example 23: SCI Echo with LCD display
finclude <hidef.h> /* common defines and macros */
#include <mc9s12dg256.h> /* derivative information */
fpragma LINKINFO DERIVATIVE "mc9sl2dg256b"
#include "main_asm.h" /* interface to the assembly module */
void main(void) {
char c;
PLLinit ()2 // set system clock frequency to 24 MHz
Ledinit () 7 // enable lcd
SCIO_ init (9600); // initialize SCIO at 9600 baud
while(l) {
c = inchar0(); =// wait for character
data8(c); // write it to the LCD
outchar0 (c); // echo it back
104 Chapter 8
The registers used for programming the SCI module are shownin Fig. 8.3. Listing
&.2 shows the assembly language routines corresponding to the six C function calls in Table
8.2.
In the subroutine SC/0_init, all of the bits in control register, SC/OCR/, are cleared to
zero. This will select 8 data bits and no parity as shownin Fig. 8.3. You canrefer to thefile
S12SCIV2.pdf, available from Freescale, for a description of the other bits in this control
register. We will always clear them to zero. The hex value $0C is stored in control register,
SCIOCR2. This will enable the SCI transmitter and receiver, while disabling all interrupts.
Finally, the subroutine SC/O_init, sets the baud rate. The baudrate is passed to the
subroutine in accumulator D. The 13-bit value, BR, to store in the baud rate registers,
SCIOBDH:SCIOBDL,is found using the formula
In our case, the BusClock is 24 MHz, and 24000000/ 16 = 1500000 = $16E360. Therefore,
we need to calculate BR=$16E360/D. The ediv instruction will divide (Y:D) by Xand
leave the quotient in Y. We must therefore first transfer D to X and then store $0016 in Y
and $E360 in D. After calling ediv, the quotient, Y, will be the value of BR to be stored in
SCIOBDH.
Whenreceiving a character, you mustfirst wait for the RDRF flag in status register 1 to go
high. In the subroutine inchar0 in Listing 8.2, the statement
will do this. Recall that this statement will branch on itself as long as the AND of SC/OSR1
and $20 is zero, 1.e. as long as bit 5 of SCJOSR/ is zero. As soon as the RDRFflag goesto 1,
a character has been received in SCJODRL, and this value is loaded into accumulator B,
which returnsthis value to the calling function in the C program.
Whensending a character, you mustfirst wait for the 7DREflagin status register |
to go high, meaning that the transmit data register is empty. In the subroutine oufchar0in
Listing 8.2, the statement
will do this by waiting for bit 7 of SC/OSR1 to go to |. As soon as the TDREflag goesto 1,
the transmit data register is empty, and the value in accumulator &, passed fromthe calling
function in the C program,is stored in SCIODRL.
The three SCI1 subroutines in Listing 8.2 are the same as the SCIO subroutines,
except that they use the SCI1 registers.
106 Chapter8
inchar0:
brclr SCIOSR1, #$20,inchar0 ;wait for RDRF
LDAB SCIODRL ;get char
RTS
outchar0:
brclr SCIOSR1, #$80, outchar0 ;wait for TDRE
STAB SCIODRL ;write char
RTS
incharl:
brcelr SCI1SR1, #$20,incharl ;wait for RDRF
LDAB SCI1DRL ;get char
RTS
outcharl:
brcelr SCI1SR1, #$80, outcharl ;wait for TDRE
STAB SCI1DRL ;write char
RTS
A circular queue is a useful data structure to use when you needto store characters
read in an interrupt service routine. The queue can then be read as necessary without
missing any of the received characters. In Example 24, wewill illustrate using a queue by
storing values read from a keypad in a queue and then displaying them all on an LCD. In
Example 25 wewill use a queue to store characters received in the SCI port using interrupts.
Serial Communication Interface (SCI) 107
A circular queue is shown in Fig. 8.4. Multiple values can be stored in this queue
before they are removed (in the same order they were stored). Therefore, characters will not
be lost if they are received faster than they are removed. Ofcourse, if the queueis full and
another characteris received,it will be lost. We will implement this queue by writing four C
functions whose prototypes are shown in Listing 8.3. The C programs for these four
functions are in the separate C file called queue.c that is shownin Listing 8.4.
The queueis defined to be an array called ghuff containing QMAXbytes. The index
of the first byte in the queue (0) is stored in the variable min and the index of the last byte in
the queue (OMAX-1) is stored in the variable max. The index values front and rearare
initialized to 0 in the C function initg( ) in Listing 8.4 and serve as pointers to the front and
rear of the queue. Tostore a value in the queue, the index rear is incremented andthe value
is stored at gbuff[rear]. However, when rear exceeds max it must wrap around to min. If
rear ever runsinto front, then the queue is full and we will back up rear and not store the
new value. The complete algorithm for storing a value in the queue is implemented bythe C
function gstore( char c) in Listing 8.4 whichstores the character c in the queue.
To read a value from the queue the index front is incremented, and the value at
gbuff[front] is read. This will guarantee that the first value stored in the queue will be the
first one read from the queue. The queue will be empty any time that front = rear. The C
function int gempty(void) shownin Listing 8.4 will return a | (true) if the queue is empty
and return a 0 (false) if the queue is not empty.
qbuff qbuff
front = rear > min front > min
38
rear —B> 45
max max
(a) (b)
Figure 8.4 A circular queue:
(a) empty; (b) containing two values
Thevariables in Listing 8.4 are definedto be static so they will not be visible to your
main program main.c. When you create a project using the stationery file
LBE_DRAGON12-Plus-USB, the files queue.c (Listing 8.4) and gueve.h shown
in Listing
108 Chapter 8
8.3, will be in the source folder of your CodeWarrior project. These will be available for
you to use by including the statement #include "gueue.h" in your main program as shown in
Examples 24 and 25 below.
Note that the default value of QMAX(the size of the queue) is 16. This is too small
for manyreal applications, so you may wantto increase it for your particular application.
void initq(void) {
min = 0;
front = 0;
rear = 0;
max = QMAX-1;
}
void qstore(char c) {
reartt; // inc rear
if (rear > max)
rear = min;
if (rear == front) {
rear--; // queve is full
if (rear < min) // rewind rear
rear = max;
}else
qbuff[rear] = c; // store c at rear
Serial Communication Interface (SCI) 109
char getq(void) {
front++; // ine front
if (front > max)
front = 0;
return qbuff[front]; // return value at front
}
()
©
nt
#include "queue.h"
#include "main_asm.h" /* interface to the assembly module */
void main(void) {
Char* blanks;
char Cc, a;
blanks = " Wis
PLLinit (); // set system clock frequency to 24 MHz
ledinit () 3 // enable l1cd
initg ()+ // initialize the queue
keypad_enable(); // enable keypad
set_lcd_addr (0x00); // display on lst line
110 Chapter 8
Table 8.3_C Function calls for the SCI port with interrupts
Function Description
void SCIO int _init(int b); Initialize SCIO with interrupts and baud rate b
char read SCIO Rx(void); Read character received in SCIO Rx port
void outchar0O(char c); Output character c out SCIO TxD pin
void SCI1l_ int _init(int b); Initialize SCI1 with interrupts and baud rate b
char read SCI1 Rx(void); Read character received in SCI1 Rx port
void outcharl (char c); Output character c out SCl1 TxD pin
|| Table 8.4 C Function calls for using the character queue in queue.c
Function Description
| void initq(void) ; initialize the queue
| void
int
gqstore(char) ;
qempty (void);
store character in queue
return 0 if queue is not empty
char getq(void); read character from queue
void main(void) {
char c;
PLLinit(); // set system clock frequency to 24 MHz
led.init (); // enable lcd
initg(); // initialize the queue
SCIO_int_init(9600); // initialize SCIO at with interrupts
while (1) {
while(qempty() != 1) { // empty the queue
c = getq(); // and display on lcd
data8(c); // write it to the LCD
outchar0 (c) ; // echo it back
Listing 8.7 gives the assembly language subroutines associated with the new C
function calls shown in Table 8.3. The only differences between the subroutines SCI0_init
in Listing 8.2 and SC/0_int_init in Listing 8.7 is that in the latter case we store the hex value
$2C in SCIOCR2, which will enable receive interrupts by writing a 1 to bit 5 (REJ) in control
register 2 in Fig. 8.3. We also need to enable hardware interrupts by including the
instruction CL/ at the end of the SC/0_int_init subroutine.
The subroutine readSCIORx in Listing 8.7 will just read the byte that has been
shifted in Rx and loaded into SC/JODRL. A dummy read of SC/OSR/ is required, because the
RDRFflag is cleared by a read of SC/OSR/ followed by a read of SCJODRL. Note how C
function read_SCIO_Rx( ) is used in the interrupt service routine in Listing 8.6 to read the
value received in the SCI port and storing it in the queue. |
Serial Communication Interface (SCI) 113
; Read Rx byte
; char read_SCIO_ Rx()
readSCIO Rx:
LDAA SCIOSR1 ; clears RDRF flag
LDAB SCIODRL ; return data
RTS
; Read Rx byte
; char readSCI1 Rx()
read_SCI1_ Rx:
LDAA SCI1SR1 ; clears RDRF flag
LDAB SCI1DRL >; return data
RTS
It is often useful to measure the acceleration at fixed time intervals (say every 10 ms)
and send this data to a Matlab programfor further analysis. Listing 8.8 shows a programthat
Will do this.
The real-time interrupt service routine (see Chapter 5) will read the x-, y-, and z-
acceleration values every 10.24 ms if the variable reading is set to | andthe arrays defined
for ax[count], ay[count], and az[count] are not full. The maximumbuffersize is defined to
114 Chapter 8
be 1024 (about 10 seconds of data) at the beginning of the program. Note that the main
program will collect acceleration data as long as you are holding down switch SWS on the
DRAGON 12-Plus-USB board.
int ax[BufMax];
int ay[BufMax];
int az[BufMax];
int count;
int reading;
void main(void) {
int i;
char* message;
int lsb,msb,al, ah;
Once you have collected the acceleration data, you must make sure that the
CodeWarrior Debug windowIs closed, and then run the Matlab function dragon2matlab( )
shownin Listing 8.9. Press switch SW2to send the data out the serial port to Matlab. This
Matlab function will collect the acceleration data and plot it on a graph. An example in
which the accelerometer was bouncedthree times is shownin Fig.8.5.
fclose(s);
dt=0.01024; tdata collected every 10.24 ms
for i=l:count
t(1)=(i-1) *dt;
end
plot(t,ax,'-r', t, ay, '-g', t, az, '-b')
700 T T T T T T T T
accelerometer output
0
0 02 04 O06 06 1 1.2 1A 16 18
Figure 8.5 Matlab plot resulting from three bounces of the accelerometer
Serial Communication Interface (SCI) 117
PROBLEMS
8.1 Modify Listing 8.1 to type your name on the PC keyboard and display it on the second
row of the LCD display as you typeit.
8.2 Write a program that uses the hex keypad described in Section 3.2. As you type
characters on the keyboard, convert the hex value to ASCII and send the character out
the SCIO port. Test the program by running a terminal program on the PC that should
display the characters that you type on the keypad.
8.3. Write a program that will collect light data from the light sensor on the DRAGON 12-
Plus-USB board every 100 ms for a period of 10 seconds when key | on the hex
keypad is pressed. When key on the hex keypadis pressed, the 100 samplesoflight
data will be sent out the serial port to a Matlab program that will plot the data as a
function of time.
118 Chapter9
Chapter 9
In this chapter we will show how the serial peripheral interface (SPI) can be
interfaced to the LTC1661 dual 10-bit D/A converter (DAC) on the DRAGON 12-Plus-USB
board and used to read up to 16 switches on a hex keypad.
BEE
PP4 (112) MISO2 Master-In-Slave-
PP5 (111) MOSI2 MasecOursioce
PP7 (109) SCK2 Serial Clock
PP6 (110) SS2 Slave Select
The Serial Peripheral Interface (SP!) 119
In the master, the bits are sent out the MOS/ (master out - slave in) pin and received
in the MZSO (master in - slave out) pin. In the slave, the bits are received in the MOSI
(master out - slave in) pin andsent out the M/SO (masterin - slave out) pin. Thebits to be
shifted out are stored in the SPI data register, SPODR, and by default are sent out most-
significant bit (bit 7) first as shown in Figure 9.1. By programming a bit in one of the
control registers, the bits can be sent out least significant bit first. At the same timethat bit 7
is being shifted out the MOSIpin in the master, a bit from bit 7 of the slave is being shifted
into bit O of the master via the M/SO pin. This bit will eventually end up in bit 7 of the
master after eight clock pulses or shifts. The clock which controls how fast the bits are
shifted out of and into SPODR is the signal SCK. The frequency of this clock can be
controlled by the SPI baud rate register. The SS (slave-select) pin must be lowtoselect a
slave. This signal can come from any pin on the master, including its SS pin whenit is
configured as an output.
MASTER ee SLAVE
MOSI
Prt
Loo to 2 lO MISO MISO
tii}
76543210 MCSI
SPODR pe SPODR
SS SS
Se J4
| LTC1661 | ale
pmMée ———CS OUTA| sooo! 4
| 2
SCKO ‘SCLK GND|
|
|
Mosio
PSS
DIN vcc | +5V 3
|
+5V | REF OUTB |
The upper 4 bits in Fig. 9.3 are a 4-bit control code, whose functions are shownin
Table 9.3. Bits 11 —2 in Fig. 9.3 are the 10 bits of the binary number whose analog output is
desired. The lower two bits in Fig. 9.3 are don’t cares.
To begin a conversion the CS pin in Fig. 9.2 is brought low. This pin is connectedto
PM6 on the MC9S12DG256 microcontroller and can be brought lowusing the Cstatement
which is equivalent to
and will force bit 6 of PTMto become 0 by ANDing PTM with LOLLLLLL.
After sending out the high byte of Fig. 9.3 andthen the lowbyte using the C function
call send_SPI0(char) from Table 9.2, the CS pin in Fig. 9.2 is brought high using the C
statement
PTM |= 0x40;
Which will force bit 6 of PTMto become | by ORing P7'M with OL000000.
The SCLK pin in Fig. 9.2 is connected to the SCKOpin (PS6) on the MC9S12DG256
microcontroller. The C function call SP/0_init( ) from Table 9.2 will initialize the SP/0port
With a baud rate of 250 KHz andshift the data into the DAConthe rising edge ofthe clock.
Listing 9.1 shows a C programthat will read the DIP switches as an 8-bit binary
number, store this data in bits 9:2 of Fig. 9.3, add the control code 1111 to load both A and B
122 Chapter 9
DAC registers, send this 16-bit word to the LTC1661 DAC, read channel 1 of the ADC1
A/D converter, and display this value on the LCD. Totest this program you must connect a
wire between OUTA from the DAC (pin | of header /4) and channel 1 of ADCI (pin PADO9
of header H6).
char oc:
void main(void) {f{
PLL, init); // set system clock frequency to 24 MHz
2dl enable(); // enable a/d converter 1
led _init(); // enable lcd
SPIO init(); // enable SPIO mode 0
SW_enable(); // enable DIP switch
DDRM = OxFF; // port M outputs
while(1l) {
val = SW1l_ dip(); // read dip switch
val = val << 2; // shift 2 bits left
val = val & Ox03FF; // 8-bit input data
val = val | OxFOOO; // add control code; load DACs A&B
wO = val >> 8; // get high byte
PTM &= OxBF; // bring PM6 low
c = sendSPIO(w0); // send high byte
c = sendSPIO (val); // send low byte
PIM |= 0x40; // bring PM6 high
val = adlconv(l1); // read channel 1 of ADCl
set_lcd_addr(0x40); // display on 2nd row of LCD
write int_lcd(val); // write value in field of 5
ms delay(100); // delay 0.1 seconds
will shift the 8-bit DIP switch reading that has been stored in the 16-bit variable va/ two bits
to the left. When doing this, it performs a sign-extend operation. This means that a DIP
switch reading of 10000000, when shifted 2 bits to the left, will be stored in val as
1111111000000000. Thus, bits D9 and D8 in Fig. 9.3 become | when they should be 0.
Wetherefore use the instruction
val = val & Ox03FF;
to force the upper six bits in Fig. 9.3 to be zero before adding the
control code with the
instructio n
val = yal | OxFOOO;
The Serial Peripheral Interface (SPI) 123
The SPI registers are shownin Fig. 9.4. There are two control registers, a baudrate
register, a status register, and a data register.
)
SPI Control eegene 1 Rear - ee) eer — Ox00F0) (SPIZCR1 0~ OxO0F8
7 2 1
Sple_| SPE SPTIE MSTR CPOL [| CPHA |] SSOE [| LSBFE | SPixCR1
SPIE = 1: SPI interrupts enabled. SPIE = 0: SPI interrupts disabled.
SPE= 1: SPI enabled. SPE = 0: SPI disabled
SPTIE = 1: SPTEF interrupts enabled. SPTEF = 0: SPI interrupts disabled
MSTR = 1: SPI in Master mode. MSTR = 0: SPI in Slave mode.
CPOL = 1: Active-low clocks selected. In idle state SCK is high.
CPOL = 0: Active-high clocks selected. In idle state SCK is low.
CPHA = 1: Sampling of data occurs at even edges (2.4,6,...,16) of the SCK clock
CPHA= 0: Sampling of data occurs at odd edges (1,3,5,....15) of the SCK clock
SSOE = 1: If MODFEN = 1, SS pin is slave select output, else SS not used by master
MN
v0 “OD
SSOE = 0: If MODFEN = 1, SS input with MODF feature. else SS not used by master
_SBFE = 1: Data is transferred least significant bit first.
LSBFE = 0: Datais transferred mostsignificant bit first.
The masterinitiates a transfer by storing a byte in the SPI data register. By default,
the bits are shifted out of SP/xDR most-significant bit (bit 7) first and received in the least-
significant bit (bit 0) as shown in Figure 9.1. If the LSBFE bit in the SPI control register 1,
SPIxCR1, is set to 1, then data is transferred least-significant bit first rather than the more
normal most-significant bit first.
The clock which controls how fast the bits are shifted out of and into SP/xDR is the
serial clock SCK. The frequency of this clock can be controlled by the SPI baud rate
register, SP/xBR, shown in Figure 9.4. The SPI baud rate is determined by dividing the bus
clock (24 MHz) by the baudrate divisor given by
BaudRateDivisor = (SPPR{2:0}+1)x gl sraizcne)
The SPI control register 1, SP/xCR/, is shown in Figure 9.4. The two bits CPOL and
CPHAcontrol the polarity and phase of the clock. If CPOL = 0, the clock idles low and data
are shifted in and out onthe rising edge of the clock if CPHA = 0, and onthe falling edge of
the clock if CPHA = 1. If CPOL = 1, the clock idles high and data are shifted in and out on
the falling edge of the clock 1f CPHA = 0, and onthe rising edge of the clock if CPHA = 1.
If CPHA = 1, the SS slave select line can remain low during successive transfers. On the
other hand, if CPHA = 0, the SS line must be deasserted and reasserted between each
successive byte of data transferred.
To use the SPI, the SPE bit in the control register SP/xCR/ must be set to 1, and to
use the SPI as the master the MS7R bit must be set to 1. Setting the SPIE bit will enable
interrupts which will cause a hardware interrupt to occur when a byte data transfer has been
completed.
Whenthe eight bits have been completely shifted out of (and/or into) the SP/xDR the
SPIFflag (bit 7) in the SPI status register, SP/xSR, shown in Figure 9.4 is set to 1. This bit
is cleared by reading the status register, SP/xSR, followed by accessing the data register,
SPIXDR,
The SSOEbit in SP/xCR/ can enable an SS output modein a master (if MODIFENin
SPIxCR2 is set to 1) in which the SS output automatically goes low during each SPI
transmission and then goes high during each idling state so that external devices are
deselected.
In the bidirectional mode, a single pin (OSI for a master and M/SOfor a slave) can
be used for both input and output. The bidirectional modeis enabled by setting bit SPCO in
the SPI control register 2, SP/xCR2, shown in Figure 9.4. When in the bidirectional mode,
bit BIDIROE in SPIxCR2 is the output enable bit. When B/D/JROE = |, the output drives the
MOST pin. When BIDIROE =(), the output buffer is in the high-impedance state, and an
input from the MOSIpin can be read.
. Listing 9.2 shows the assembly language routines corresponding to the twelve C
function calls in Table 9.2. In the subroutine SP/0_init, the hex value $53 is stored in
SPIOBR, whichsets the BaudRateDivisor to
BaudRateDivisor = (S + 1) x 2°") = 6x1l6= 96
The SPI baud rate is then 24 MHz/96 = 250 kHz. The next statement moves the hex value
$50 into SPIOCR/. This enables the SPI as a master by setting the SPE and MSTRbits, and
clears both CPOL and CPHA bits to zero. This meansthat the clock idles low and data are
shifted in and outon the rising edge of the clock. Clearing all bits in SPJOCR2 disables the
MODFfeature. Finally, the SSO pin is configured as an output by setting bit 7 of the data
The Serial Peripheral Interface (SP!) 125
direction register, DDRS. The output of this SSO pin is then cleared to zero. Similar
subroutines are used to initialize SPI1 and SPI2.
¢ void SPI1_init();
SPI1_ init:
movb #$53, SPI1BR ;divide 24 MHz clock by
movb #$50, SPI1CR1 ;master, CPHA=0, CPOL=0
clr SPI1CR2 ;disable MODF
bset DDRP, #$08 7SS1 an output port
bclr PTP, #$08 7SS1 = 0
rts
* SS1_HI();
SS1 HI:
bset PTP, #$08 ;set bit 3 of port P
rts
» SS2 HI();
SS2 HI:
bset PTP, #$40 ;set bit 6 of port P
rts
; set SS low
; SSO_LO();
SSO_ LO:
bclr PTS, #$80 ;clear bit 7 of port S
rts
; SS1_LO();
SS1_LO:
bclr PTP, #$08 :clear bit 3 of port P
rts
; SS2_LO();
SS2_LO:
bclr PTP, #$40 ;clear bit 6 of port P
rts
The subroutine sendSPIO mustfirst wait for the SPTEF bit in the status register,
SPIOSR, to go to 1, indicating that the SPI data register is empty. The character passedto the
subroutine in accumulator B from the C program is then stored in the SPI data register,
SPIODR. The subroutine then waits for the SP7EFbit in the status register, SP/OSR, to go to
1, indicating that the character has been sent. Finally, the byte received in the data registeris
loaded into accumulator B, which is returned to the C program. Similar subroutines are used
to send andreceive data in SPI] and SPI2.
The subroutine SSO H/ will set pin SSO (bit 7 of port S) to | and the subroutine
SSO_LO will clear SSO to 0. The subroutine SS/_H/ will set pin SS/ (bit 3 of port P) to 1
and the subroutine SS/_LO will clear SS/ to 0. Finally, the SS2_H/will set pin SS2 (bit 6 of
port P) to | and the subroutine SS2_LO will clear SS2 to 0.
one side of each key is connected to a commonground. In this section, we will show how
the SPI port can be used to read this type of 16x1 hex keypad by using 74165 shift registers.
A 16x1 hex keypad (or any collection of 16 switches) can be connected to two
74165 shift registers as shown in Fig. 9.5. In this case, one side of each switch is connected
to ground. The 74165 is an 8-bit parallel in/serial out shift register. The other side of each
switch is connected to one of the parallel inputs (4-H) of the shift register. If pin |
(SH/~LD) of the 74165 is brought low, the values on the eight parallel inputs are latched into
the shift register. When the SH/~LDpin is high and the CLK INH pin ts low, then on the
rising edge of the CLK input the eightbits in the shift register are shifted onebit to the right.
Bit A is shifted to B, B to C, etc. Bit G 1s shifted to H which shows up ontheserial output
pin, Oy. In Fig. 9.5 the output Oy of the lower 74165 is connected to the serial inputpin,
SER, of the upper 74165. The output Oy of the upper 74165 is connected to the ‘SO pin
of one of the SPI ports in the MC9S12DG256.
In Fig. 9.5, the SPI signal SCK is connected to each CLK pin ofthe two 74165 chips
and the SPI signal SS is connected to each SH/~LDpin ofthe two 74165 chips. Note that the
MOSpin of the SPI port is not connected to anything. We are only interested in receiving
bytes in the MISO pin. Todo this, of course, we must write a dummy value (say zero) to the
SPI data register, SPJODR, (by calling our C function sendSPI0(0)) andit will be shifted
out the unconnected MOSI pin at the sametime thatthe desired byte is being shifted in the
MISOpin.
Notice in Fig. 9.5 that pin H of the upper 74165 (key 3) will be the first bit shifted
out. This will end up in the most-significant bit of the first byte transferred. After
a
transferring one byte, the register contents of the lower 74165 will have beenshiftedinto the
cadineneeateeiee
upper 74165 shift register. The value associated with key 7 will nowbe at the output Oyof
the upper 74165. After a second byte is transferred, this key 7 value will be at the most-
significant bit location of this second byte. If the first byte transferred becomes the most-
significant byte of a 16-bit word then thebits of this 16-bit integerwill be associated with the
16 hex key values as indicated in Fig. 9.6.
The C function read_/6shift( ) shown in Listing 9.3 will return the 16-bit value
shownin Fig. 9.6. It does this by shifting in two8-bit bytes through the SPI port. Note that
the high byte is read first, shifted 8 bits to the left, and then ORed with the lowbyte.
Note from Fig. 9.5 that if no key is being pressed, thenall ofthe parallel inputs to the
shift registers are pulled high. This means that all of the bits in Fig. 9.6 will be set to I.
Thus,the value ofthis 16-bit value will be OxFFFE. If any key was being pressed when the
function read_l6shifi( ) is executedthen the bit associated with that key will be zero. The
function ger_key( ) shown in Listing 9.4 will search forthe bit in Fig, 9.6 thatis clearedto
Zero. It does this by ANDing data with a mask with onlyasingle bit set and checking to see
If that bit was zero. The mask starts with the most-significant bit set (Ox8000) which
Corresponds to bit number 0 in Fig. 9.6 and then shifts the bit right each time through the
While loop by using the statement
SE
128 Chapter9
+5V +5V
SCK SS
V 3.3K : $ 3.3K
1 acc Nal 1
SH/_LD Veco
p— Scuk cLkint3
0 —1_
Ste pH4 i
g
: OTF zaigs CC 9
2
scion
© 2 G B 2 —tt“sa
3 [- e—__4 jt_¢—| 3
+ an, sero 1
2 Gnp Qh 2 —= MISO
eae, Net
LISHLD VetLB
S4eLK CLK INH
—— 3 14 —.
4;>- -&—— TE D e°® —-C
tL 4 13 —l
6
—t 5 G B
12 —L E
7
—— 6 H A
W —L F
tL a, SERLE L
1 eno Qt
5a 333 3.3K
+5V +5V
mask >>= 1;
which is equivalent to
Welabel the most-significantbit in Fig. 9.6 as 0 rather than 15 so that this bit number
will correspond to the index value in the table keytb/[ ] shown in Listing 9.4. When a zero
bit value is found, the value of keytb/[i] will be the hex value of the key being pressed and
this value is returned as the value of the function gerkey( ). Note that if no keyis being
pressed the value of get_key( ) is 16.
Listing 9.5 shows a main program that will use the two functions given in Listings
9.3 and 9.4. This programwill wait for you to press a key and thendisplay the key value on
the LCD.
130 Chapter 9
}
}
return key;
void main(void) {
char key;
PLLinit(); // set system clock frequency to 24 MHz
led_init(); // enable led
SPIO init(); // enable SPIO
set_lcd_addr (0x40);
while(1) {
key = get_key();
if (key < 16) {
key = hex2asc(key); // convert to ascii
data8 (key) ; // display on led
}
The Serial Peripheral Interface (SPI) 131
PROBLEMS
9.1 The Analog Devices AD7376 is a+15 V Operation Digital Potentiometer. A
simplified block diagram is shown below.
SD0 —
AD7376 p— A
SDI — Digtal |
CLK —4 Potntioreter
i B
CS —
Chapter 10
Timer
Each of the eight pins of port T can be programmedto output pulsetrains using the
output compare function. The way it works is that each pin has a 16-bit output compare
register associated with it, into which you can write a 16-bit value. When the free-running
up counter, TCNT, reaches the value in the output compare register, you can make any
number of things happen. For example, you could have the pin go high, or go low, or
toggle. You could cause an interrupt to occur on an output compare match at which time
you could update the output compareregister for the next event you want to happen.
[t turns out that pin 7 of port T (P7T7) is special because it can be used in
conjunction with any other pin to produce some useful effects. For example, suppose we
want to produce the pulse train shown in Fig. 10.1. We can set it up such that on a TC7
match (i.e. when the value of the free-running counter, TCNT, is equal to the contents of the
output compare register, 7C7) the signal on pin PT7T6 will go high. We canalso set it up
such that on a TC6 match(i.e. when the value of the free-running counter, TCNT, is equalto
the contents of the output compare register, 7C6) the signal on pin PTT6 will go low. If the
value in 7C6 is pwidth greater than the value in 7C7 then the first pulse in Fig. 10.1 will
occur when TCNTpasses these two values. But howdo we getit to produce the second
pulse one period later? The answeris that we cause an interrupt to occur on the falling edge
of the pulse train (i.e. on a TC6 match), and in this interrupt service routine we will update
the values of 7C6 and TC7 based on the values of pwidth and period. The newvalue of TC7
will be the old 7C7 plus period and the new value of TC6 will be the new value of TC7 plus
Timer 133
pwidth. Note that it doesn’t matter if these sums exceed OxFFFF because the sum will
simply wrap around as will the counter TCNT.
TC6 match
ef period ———_———_ pwidth ~<a
TC7 match
Figure 10.1 Pulse train
To makeit easy, we have written assembly languageroutines that can be called from
the C functions shown in Table 10.1. The first two functions in Table 10.1 can be used to
producethe pulsetrain in Fig. 10.1. These functions will be used in Example 29. Thelast
four functions in Table 10.1 will be used in Example 30 to play musical notes on the built-in
speaker when keyson the keypad are pressed.
void main(void) {
PLL. init (); // set system clock frequency to 24 MHz
ptrain6 init();
period = 5734;
pwidth = 2867;
while(1) { // do nothing while generating pulse train
}
The registers associated with the timer are shown in Table 10.2. All timer functions
are based arounda single, free-running 16-bit up counter, TCN7, shownin Fig. 10.2. The
address of TCNT ($0044) is the address of the high byte of TCNT. The contents of TCNT
should be accessed as a word so asto read the real 16-bit value stored in TCNT.
You mustfirst enable the timer bysetting bit 7 of the timer system controlregister1,
TSCR1, shown in Fig. 10.3. Thus, the assembly languageinstruction
movb #$80,TSCR1
will enable the timer, and any time you access TCNT you will obtain a newcounter value.
oO
TEN TSWAI TSFRZ TFFCA 0 0 0 0 TSCR1
TEN = 1: Timer enabled. TEN = 0: Timer disabled.
TSWAI — Timer Module Stops While in Wait
TSFRZ — Timer and Modulus Counter Stop While in Freeze Mode
TFFCA — Timer Fast Flag ClearAll
The value of TCNTis incremented at a rate that dependsonthe three bits, PR2: PRO,
in the timer system control register 2, TSCR2, shownin Fig. 10.4 according to the formula
The default values of PR[2:0] are 000, so if the bus clock rate is 24 MHz, then the timer
clock rate will be 24 MHz and TCNTwill increment once about every 41.6 ns. This means
that the counter will overflow (go from $FFFF to $0000) about every 2.73 ms. Bychanging
these three bits in TSCR2 youcan divide the clock rate by 2, 4, 8, 16, 32, 64, or 128. These
bits may be changedat any time; however, the change will not take effect until the next time
that all prescaled counter stages are zero. For a 24 MHz busclock, the slowest timer clock
——_ ee ee
rate would be 24 MHz/ 2’ =187.5 kHz, at which rate the timer would overflow about every
350 ms.
136 Chapter 10
Whenthe timer overflows the TOF bit of TFLG2 (main timer interrupt flag 2) is set
to 1 as shown in Fig. 10.5. This flag is cleared by writing a 1 to bit 7 (TOF) of TFLG2.
Althoughthis may seem strange,it is the standard wayof clearing flags in the HCS12.
Each pin of Port T can be selected to act as either an input capture or an output |
compare. This selection is done by setting the bits in the timer input capture/output compare
select register, 7/OS, shownin Fig. 10.6.
The HCS12 has eight 16-bit timer input capture/output compare registers, T7Cn, as
shownin Figure 10.7. Note, for example, that the address of TC2 1s $0054-$0055 and the
address of TC7 is $005E-SOOSF.
Whenthe value of the free-running counter, TCNT,is equal to the value stored in one
of the output compareregisters, the corresponding output compare channel flag, Cx’, in the
main timer interrupt flag 1 register, TFLG/, is set as shownin Figure 10.8. Note that this
flag is cleared by writing a 1 to the correspondingbit position.
The eight output compares, OCO—OC7,are associated with pins PTT0-PTT7of Port
T. As described above when the free-running counter, 7CNT7, matches the value in one of
the output compare registers, 7Cn, shown in Fig. 10.7, the corresponding output compare
flag, CxF, in TFLG/ is set as shown in Fig. 10.8. When this occurs, it is possible to cause
the output of PTT0-PT77to change. In this way we can produce output waveformsonpins
0-7 of Port T.
In addition to the registers shown in Figs. 10.7 and 10.8, the registers shown in
Figure 10.9 also are used for output compares. Output compare 7 can control the outputs of
any of the pins PTT0—PTT7. On the other hand, output compares 0-6 can control only their
own output pins. The output compare 7 mask register, OC7M, and the output compare 7
data register, OC7D, are used by output compare 7 to control the outputs on pins P7TT0-
PTT7. Setting an output compare mask, OC7Mx, in OC7Mwill enable the corresponding
output pin x. If this mask bit is set, then the contents of the corresponding bit, OC7Dx,in
OC7D will determine whether pin x of Port T will go high or lowon a successful match of
output compare 1. For example, if OC7M6is set to 1, and OC7D61s cleared to 0, then ona
successful match of output compare 7 (TC7) the value on PTT76 will go low.
It is possible to have output compare 7 (7C7) and output compare 6 (7C6) both
control the output of pin P7T6 at the same time. The waythat 7C6 controls pin PTT6 is
determined by the two bits, OM6 and OL6, in register TCTL/ as shownin Figure 10.9. For
ne
example, if OM6 = | and OL6 = 1, then pin PT76 will be set to | on a successful match
of output compare 6.
Timer Control Register 1/ Timer Control Register 2 (TCTL1 — 0x0048) (TCTL2 = 0x0049)
¢ 6 5 4 3 2 1
OM7 OL7 OM6 OL6 OM5 OL5 OM4 OL4 TCTL4
OM3 OL3 OM2 OL2 OM1 OL1 OMO OLO TCTL2
OMn — Output Mode OLn — Output Level
You can cause an interrupt to occur on an output compare match by setting the
corresponding bit in the timer interrupt enable register shown in Fig. 10.10. The timer
overflow interrupt enable bit, TO/, is bit 7 of the timer system control register 2, TSCR2,
shown inFig. 10.4.
Sometimes you may want to force an output compare event to occur before TCNT
reaches the value stored in the output compare register TCn. You can dothis by writing a 1
to the corresponding bit, FOCx, in the timer compare force register, CFORC, shownin Fig.
10.11. When you do this the output compare flag, CxF, in TFLG/ is not set and no interrupt
will occur. Only the output compare event, such as toggling the Port T pin will occur.
Listing 10.2 shows the assembly language routines corresponding to the first two C
function calls in Table 10.1. In the subroutine ptrain6_init, the hex value $CO is stored in
the Timer Input Capture/Output Compare Select Register, 7/OS, (see Fig. 10.6) which will
makebits 6 and 7 of Port T output compares.
The secondinstruction in the subroutine ptrain6_init in Listing 10.2 stores the hex
value $04 in TSCR2 (See Fig. 10.4), which will set the timer clock rate to
24 MHz/2* =1.5 MHz. Thetimer is then enabled bysetting the TEN bit (bit 7) in the timer
system control register 1, TSCR/ (see Fig. 10.3). The current value of TCNTis then stored
In Output compare registers, TC6 and TC7.
Bits 6 of registers OC7M and OC7D(see Fig. 10.9) are set to 1, which will make pin
PTT6 go high on a TC7 match. To make pin PTT6 go low on a TC6 match, OM6(bit 5) in
TCTLI (see Fig. 10.9) is set to 1 and OL6 (bit 4) in TCTL/ is cleared to 0. To enable TC6
interrupts, weset bit 6 of the timer interrupt enable register shown in Fig. 10.10. Finally, we
must enable all hardware interrupts by clearing the interrupt mask bit (I) in the condition
code register using the instruction cli.
The subroutine ptrain6 shown in Listing 10.2 is called by the output compare
interrupt service routine in Listing 10.1. The value of pwidth is passed to the subroutine in
accumulator D and the value of period is pushed on the stack and located at sp + 2. After
saving the value of pwidth by pushing D onthestack, the value of period (which is now at sp
+ 4) is loaded into D and added to the current value in TC7, with the result stored back in
Timer 139
TC7. The next match of TC7 will cause the output on pin P7T76 to go high as shownin Fig.
10.1. The value of pwidth, now on the top ofthe stack, is added to this new value in TC7
and stored in 7C6. The next match of TC6 will cause the output on pin PTT6 to go low as
shown in Fig. 10.1. The value of pwidth, whichisstill on top of the stack, must be pulled
from the stack so that the return address will be back on top of the stack. Before returning
from the subroutine ptrain6, both the C7F and C6F flags must be cleared by writing the
value $CO to TFLG/(see Fig. 10.8).
The C functions sound_on( ) and sound_off{( ) in Table 10.1 turn the sound on and off by
enabling and disabling the timer andinterrupts.
= -
| pitch '
| |
| | |
( __| ‘a
4 A
| |
TC7 match TC5 match
Figure 10.12 Square wave used to generate sound
The pitch values for different notes in the musical scale are shown in Table 10.3.
Listing 10.3 is a C program that will play two octaves of the musical scale when you press
keys on the keypadstarting on the lower left of the keypad.
Listing10.3 Example 30
// Example 30: Sound Example - play notes with keypad
#include <hidef.h> /* common defines and macros */
#include <mc9s12dg256.h> /* derivative information * /
Char k;
int pitch;
int pitchval[16] = {
d, A, B, CC, D, E, F, g, a, b, DD, G, C, f, c, e
Char * pitchdisp[16] = {
a, A "Ep. “eC, =p". ors me. oo",
Void main(void) {
PLLinit(); // set system clock frequency to 24 MHz
keypad_enable(); // enable the keypad
led_init(); // initialize LCD
While (1) {
set_lcd_addr (0x00); //set cursor to first line
k = getkey(); //get keypad button pressed
type lcd(pitchdisp[k]); //display note on led
Pitch = pitchval[k]; //pitch value of button pressed
Sound init();
soundon(); // start playing the note
wait keyup (); //wait for button to be released
sound_off(); // stop playing the note
Clearlcd();
142 Chapter 10
Listing 10.4 shows the assembly language routines corresponding to the last four C
function calls in Table 10.1. The subroutine sound_init is almost the same as the subroutine
ptrain6_init in Listing 10.2 except that channel 5 replaces channel 6 and no interrupts are
enabled. The subroutine sound_on in Listing 10.4 turns the sound on by enabling the timer
and enabling hardware and 7C5 interrupts. The subroutine sound_off in Listing 10.4 turns
the sound off by disabling the timer and disabling hardware and 7C5 interrupts.
The subroutine fone shown in Listing 10.4 is called by the output compare interrupt
service routine in Listing 10.3. The value of pitch is passed to the subroutine in accumulator
D. After saving the value of pitch by pushing D onthestack, this pitch value is added to the
current value in 7C5, with the result stored back in 7C7. The next match of TC7 will cause
the output on pin P7T5 to go high as shownin Fig. 10.12.
The value of pitch is pulled from the stack, added to this new value in 7C7, and
stored in 7C5. The next match of TC5 will cause the output on pin P7T5 to go low as shown
in Fig. 10.11. Before returning from the subroutine tone, both the C7F and C5F flags must
be cleared by writing the value $AO to 7FLG/(see Fig. 10.8).
; sound_on()
soundon:
movb #$80,TSCR1 7;enable timer
bset TIE, #$20 ;enable TCS interrupts
cli 7;enable interrupts
rts
; soundoff ()
soundoff:
sei ;Gisable interrupts
clr TSCR1 ;disable timer
belr TIE, #$20 ;disable TC5 interrupts
rts
Timer 143
In Example 29 we generated a pulse train using the output compare feature ofthe
timer module. The input capture feature of the timer module allows youto capture the value
of the free-running counter, TCNT, into the input capture register, 7Cxy, when a rising or
falling edge (or both) occurs on the associated input pin P77x.
The C function calls in Table 10.4 are assembly language routines that will allow you
to measure both the high and low times of an input pulse train on pin P77/. The function
HILO1_init( ) will enable TC/ interrupts on both edges ofthe pulse train. The timer clock is
set to 1.5 MHz so that the maximum high or lowpulse width that can be measured Is
65 ,535/1.5 MHz = 43.7 ms.
The function H/LOtimes/( ) is called in yourinterrupt service routine. It remembers
the value of TC/ that wasread at the last interrupt and subtracts this value from the current
reading of TC/ to get the pulse width. It reads pin P77/tosee if the interrupt occurred on a
rising edge (in which case the measured pulse width is low) or on a falling edge (in which
case the measured pulse width is high). This function saves the most recent readings in two
assembly language variables. You canget these values from your C programbycalling the
two functions getH/timel() and get_LOtimel().
Timer Control Register 3/ Timer contro! megister 4 (TCTL3 — 0x004A) (TCTL4 — 0x004B)
7 6 5 2 1 O
EDG7B EDG7A EDG6B EDG6A EDG5B EDG5A EDG4B EDG4A TCTL3
EDG3B EDG3A EDG2B EDG2A EDG1B EDG1A EDGOB EDGOA TCTL4
EDGnB, EDGnA — Input Capture Edge Control
Listing 10.6 shows the assembly language routines corresponding to the last four C
function calls in Table 10.1. The subroutine H/LO/ init in Listing 10.6 first configures
channel 1 to be an input capture port by clearing bit | of 7/OS. It then sets the tmer rate to
1.5 MHz by writing $04 to TSCR2 (see Fig. 10.4) and enables the timer bysetting bit 7 of
TSCRI (see Fig. 10.3). After loading the current value of TCNTinto 7C/, it configures
TCTL4 (see Fig. 10.13) to capture (and interrupt) on both rising andfalling edges ofthe
input signal. Writing a 1 to bit 1 in TFLG/ (see Fig. 10.8) will clear any old flag. Finally,
TCI interrupts are enabled bysetting bit | of 7/E (see Fig. 10.10), and hardware interrupts
are enabled with the c/i instruction.
The subroutine H/LOtimes!] shown in Listing 10.6 is called by the input capture
interrupt service routine in Listing 10.5. The first instruction loads D with the contents of
TC1, which will be the value to TCNT captured whenthe interrupt occurred. This value is
saved by pushing it on the stack. The value capturedat the last interrupt, stored in 7C/o/d,
is then subtracted from the current value in D. Bit | of P7T is then tested to see if the
interrupt occurred because of a rising or falling edge of the input signal. If theinterrupt
occurred because of a rising edge ofthe input signal, the branch will not occur andthe value
of D (the time since the last interrupt) is stored in the vanable LOftime?. Otherwise, the
branch will occur and the value of D is stored in the variable A/_time/. In either case, the
instruction pu/d will get the saved value of TC/, which was captured by the interrupt, and
store this value in 7Clo/d — ready for the next interrupt. Before returning from the
subroutine, the C/F flag is cleared by writing a | to bit | of TALG/ (see Fig. 10.8).
The subroutine getH/_time/ in Listing 10.6 simply load D with the value in
HI_timel (which is continually being updated in the interrupt service routine) and returns
this value to the C function int get_H/_time/(). Similarly, the subroutine ver LO time! in
Listing 10.6 will load D with the value in LO_time/ andreturn this value to the C function
int get_LO_timel().
146 Chapter 10
ILO1 init:
I
ILOtimesl1:
ldd TCl
pshd ;save TCl
subd TClold ;LO_time’
brcelr PTT,$02,HL1 ;if PTT1 is hi, rising edge
std LOtimel; ;store LOtimel
bra HL2
HL1: std HI_timel; ;save HI_timel
HL2: puld ;get TCl
std TClold; ;TClold = TCl
bset TFLG1, #$02 ;clear int flag
rts
get_HI_timel:
ldd HI_timel
rts
get_LOtimel:
ldd LO_timel
rts
Fuzzy Control 147
Chapter11
Fuzzy Control
In this example we will show howto use the DRAGON 12-Plus-USB board as a fuzzy
controller. Before reading this example you should read Appendix E.
In Appendix E we show that the design of a fuzzy controller consists of the three
parts shown in Fig. 11.1. The crisp inputs are first mapped to fuzzy sets using geft_inputs( ):
the fuzzy rules are then applied to the input fuzzy sets using fire_ru/es( ); and then a
defuzzification operation is performed on the output fuzzy sets to produce a crisp output
usingfind_output( ).
In this example we will design a fuzzy controller
that will keep a ping-pong ball floating at the center ofa INPUTS
vertical, Plexiglas cylinder. The position of the ball could | |
be measured using an ultrasonic transducerat the bottom of Y Y
the cylinder. Two consecutive position readings can be Mapto Fuzzy Sets get_inputso:
used to determine the instantaneous velocity of the ball.
The output of the fuzzy controller will be a signal that will FUZZY RULES
control the speed of a muffin fan at the bottom ofthe fire mules.
If AAND B then L
cylinder that blows air up the cylinder to keep the ping-
pong ball at the desired height. A second ultrasonic
transducer outside the cylinder could measure the height of
your hand above the floor, and the fuzzy controller could
ourputd):
makethe ping-pong ball follow your hand! Detueziica
iff
ton 1
find_o utp
The first step in the design is to define the !
membership functions for the inputs. The two inputs to
the controller will be the ball_position and ball_speed and Figure 11.1 A fuzzy controller
we will use the two sets of membership functions shown
in Fig. 11.2. To use the built-in HCS12 fuzzy control assembly language instructions the
values of ball_position and ball_speed must be an 8-bit number between O and 255.
The second step is to define the output motor power. The output membership
functions are defined as singletons and are shown in Fig. 11.3. Again the value of
motor_power must be between0 and 255.
The third step is to determine the fuzzy rules. These will be commonsense rules
based on the two inputs, ba/l_position and ballspeed, and the output, motor_power. It Is
convenient to represent these rules in the form of a 5 x 5 fuzzy K-map of the form shownin
Fig. 11.4. The entries in this fuzzy K-map are the membership functions of the output,
motor_power.
148 Chapter 11
| l { 1 T
0 50 100 _ 150 200 250
ball_position
| | | I t
0 50 100 150 200 250
ball_speed
| | ]
0 50 100 150 200 250
motor_power
For example,if the ball_position is zero_p (at its desired location) and the ball_speed
is zero_s (it is not moving), then the change inmotor_power should be zero_m (no change).
This is the center entry in Fig. 11.4.
If the ball_speed is zero_s (the center row in Fig. 11.4) and the ball_position is
neg_close (a little below the desired location) then we should increase the fan speed little
by setting mofor_powerto pos_low. lf the ball_position is neg_far (a lot below the desired
location) then we should increase the fan speed a lot by setting motor_powerto pos_high.
Similar arguments will hold if the ballposition is pos_close or pos_far\eading to values of
motor_power of neg_low(decrease fan speeda little) and neg_high (decrease fan specd lot)
respectively.
If the ball_position is zero_p (the center columnin Fig. 11.4) and the ball_speedis
neg_slow(ball is falling slowly through the desired location) then we should increase the fan
speed a little by setting motor_powerto pos_low. lf the ball_speed is neg_fast (ball is falling
rapidly through the desired location) then we should increase the fan speed lot bysetting
motor_power to pos_high. Similar arguments will hold if the ba//_speed is pos_slow or
ee
Fuzzy Control 149
pos_fast leading to values of motor_power of neglow (decrease fan speed a little) and
neg_high (decrease fan speed a lot) respectively.
ball_position
Similar arguments can be madefor the four entries in each of the four corners of the
fuzzy K-map in Fig. 11.4. Note that the same fuzzy output membership function tends to
occur on diagonal lines going from the upper-left to bottom-right of the diagram in Fig. | 1.4.
This is typical of many fuzzy controller rules.
The HCS12 assembly language containsthree sets of instructions that are useful for
implementing a fuzzy controller. The MEM instruction will fill a weights array given an
input and a set of membership functions. We will describe a C function call that uses this
instruction in Section 11.2. The REV instruction is used to fire the rules. We will describe a
C function call that uses this instruction in Section 11.3. The WAVinstruction is used to
calculate the output defuzzification centroid. We will describe a C function call that uses
this instruction in Section 11.4.
The first step in designing a fuzzy controller is to define the membership functions
for all inputs and the output. Each membership function can be defined by the four
parameters wl, u2, u3, and uw4 shown in Fig. 11.5. The AEMinstruction requires that the
values uJ and w4 be 8-bit values between $00 and $FF. The weight values also range from
$00 to $FF where $FFrepresents a weightvalue of1.0 in Fig. 11.5.
The MEMinstruction does not use the parameters u/, u2, v3, and u4 shown in Fig.
11.5 to define the membership function. Rather it uses w/ (called point_1) and u4 (called
point_2) together with the values of the two slopes, s/ope_1 and s/ope_2, shownin Fig. 11.5.
The value of s/ope_/ is $FF/(u2 - w/) and the value of s/ope_2 is SFF/(u4 - 3).
These values can range from $01 to S$FF. If ul = u2 or u3 = v4 then the slope is really
infinite. In this case the values of s/ope_/ and/or slope_? are taken to be SOO inasmuch as
this value is not used otherwise. A special case is a singleton, or "crisp" membership
function. This can be definedbysetting w/ = u4 and s/ope_/ = slope_2 = $00.
In the example program shownin Listing 11.1 we have allowed you to enter the
membership functions for ball_position and ball_speedusing the parameters w/, u2, u3, and
u4 from Fig. 11.5 in the arrays ball_position[ ] and ball_speeadl ]. The function
get_slopes(const unsigned char dall[ }, unsigned char memb[ ] ,int maxsize) in Listing 11.1
150 Chapter 11
will then fill the arrays memb_pos[20] and memb_speea[20] with the point_1, point_2,
slope_1, and slope_2 format used by the MEM instruction.
wt
ul u4
OO
N
c
Cc
Figure 11.5 A membership function is defined in terms of u1, u2, u3, and u4
The MEMinstruction requires accumulator A to contain the input value x; and index
register X to point to a data structure containing the two points and slopes that define the
membership function as shown in Fig. 11.6. Index register Y points to the element of the
array weigh/(j) corresponding to membership function /.
The MEMinstruction will compute the weight value at the input value x; based on the
membership function whose parameters are pointed to by X. The computed weight value
($00-$FF) is stored in the byte pointed to by Y. After the MEM instruction is executed XY
will have been incremented by 4 and Y will have been incremented by 1. If the four
parameters of all membership functions for a single input are stored in adjacent bytes of
memory, then X will be pointing to the parameters of the next membership function.
Similarly, Y will be pointing to the next element in the array weight(j).
A | Xj _|
X --> point_1 Y -->
point_2 Y+1-->
slope_1
slope_2
X+4-->
Listing11.1a Example 32
// Example 32: Fuzzy Control -- ping-pong ball
#include <hidef.h> /* common defines and macros */
#include <mc9s12dg256.h> /* derivative information */
#pragma LINK_INFO DERIVATIVE "mc9s12dg256b"
#define NUM_MEMB = 5;
void main(void) {
// input membership functions
const unsigned char ball position[] = {
0, O, 30, 60, // neg_far
40, 60, 80,110, // neg_close
90,120,135,165, // zero_pos
150,175,185,220, // pos_close
200,220,255,255 // pos_far
};
const unsigned char ballspeed[] = {
0, O, 20, 60, // negfast
35, 60, 80,110, // neg_slow
80,130,130,180, // zero_speed
155,175,185,215, // pos_slow
195,220,255,255 // pos_fast
}?
unsigned char memb_pos[20];
unsigned char memb_speed(20]
PLL_init();
si ti on //emset
,m e) ;
s,maxsizclock
b_posystem frequency to 24 MHz
get_ slopes (b al l_ po
get slopes (ball_spee d,memb_speed,maxsize) ;
Fuzzy Control 153
fire_rules()
Clear out array;
for j = 1, num_rules
{
min_owt = 1;
for i= 1, num_inputs
{ ;
wt = weight; [A*5]
if wt < min_wt
minwt = wt;
}
out([L5] = MAX(Out[Lj], min_wt);
The setup required for the 68HC12 REV instruction is shown in Fig. 11.8. Index
register Y points to an inout_array that contains the input weight(/) arrays and ends with the
guttky array described above. The elements of the weig/tj) and out(k) arrays are assigned
offsets (0 - 14) which represent the various membership functions such as neg_far and
pos_high as shownin Fig. 11.8 and Listing 11.1a.
The array Jabeled ru/es in Fig. 11.8 contains a series of bytes, pointed to by index
register X, that contains an encoding of all the rules. Each rule is of the form
The offsets in the inout_array corresponding to neg far (0) and neg_fast (5) are
stored in the first two bytes. This is followed bya byte containing SFE which separates the
inful aritccedents from the output consequents. The next byte contains 14, the offset of
nt ce
Fuzzy Control 155
pos_high in the inoutarray. This is followed by another $FE which separates the last
consequent offset from the first antecedent offset of the next rule. A byte containing $FF
marks the end of the rules. This rule encoding scheme will allow any numberofinputs and
any number of outputs.
inout_array rules
Yo neg_far O weight_pos[] x> x1 is neg_far
neq close 1 x2 is neg_fast
zero_pos 2
pos_ciose 3 y is pos_high
pos_far 4
neg_fast 5 weight_speed[] x1 is neg_far
neg slow 6 x2 is neg_slow
zero_speed 7
pos_siow 8 y is pos_high
pos_fast 9
neg _high 10 | Out[]
neg_low 11
zero_m 12
pos_low 13
pos_high 14 A = SFF end of rules
ball_position
neg_far negz_close zero_p pos_close os_far
0 I 2 g 4
pos_fast zero_motor Tneg_low neg_high neg_high neg_high
9 12 11 I 10 10
pos_slow pos_low zero_moto neg_low neg_high neg_high
13 r 11] 10 10
12
ball_speed zero_speed pos_high pos_low zero_moto neg_low neg high
7 14 13 r 1] 10
12
neg_slow pos_high pos_high pos_low zero_moto neg_low
6 14 14 13 r 11
12
neg_fast pos_high pos_high pos_high pos_low 7ero_moto
14 14 14 13 r
12
You may wonder how the REV instruction can tell the difference between the $FE at
the end of the antecedents and the $FE at the end of the consequents. The answeris thatit
uses the overflow bit V in the condition code register as a flag to tel] the difference. This bit
is automatically set to zero when the statement LDAA #3$FF is executed. The REV
instruction then toggles this bit to 1 when it encounters the SFE at the end of the antecedents
are Clears it to zero by reloading accumulator A with $FF when it encounters the $FE at the
end of the consequents.
We have written an assembly language routine that is called by the C function void
SJire_rules(unsigned char*® inout_array, unsigned char* rules, unsigned char* out, int
numout) that will first clear the out array. It then points to the ru/es[ ] array with index
register Y and to the inout_array[ ] with index register Y, sets accumulator A to SFF and calls
the REVinstruction. At this point the owf[ ] array is filled with the appropriate output
weights based on the entire list of fuzzy rules.
We will always use singleton fuzzy sets for the output represented by the centroids,
cent*, We will also use the MIN-MAX inference rule described in Appendix E. It should be
clear from Fig. E.11 in Appendix Ethat in this case the centroid jy will still be given by Eq.
(E.11) where WA is nowthe output array, out(k), shown in Fig. 11.8 and computed by the
function fire_rules(...) described in the previous section.
Once the function fire_riu/es(...) has filled the output weight array out(k) the function
Jindoutput(...) will calculate the centroid yg using Eq. (E.11) in Appendix E. The
pseudocode for the function find_output(...) is given in Fig. 11.10. The centroids of the O
output membership functions are stored in the array cent.
find_output()
numer = 0;
denom = 0;
for k = 1, Q
if out[k] != 0
{
numer = numer + out[k]*cent[k];
denom = denom + out[k];
yO = numer/denom;
The values of numer and denom in Fig. 11.10 can easily be calculated using the
HCS12 WAV instruction. If index register X points to cent[k], index register Y points to
out{k], and accumulator B contains the number of output membership functions, Q, then the
HCS12 WAV instruction will compute a 24-bit value for numer and store the result in Y-D
and compute a 16-bit value for denom and store this result in X. Therefore, if the WAV
instruction is followed by the instruction ED/V (see Appendix C) then the centroid value vg
will be left in Y.
We have written an assembly languageroutine that is called by the Cfunction
unsigned char calc_output(unsigned char* out, unsigned char* cent, int numout) that will
return the crisp output by using the WAV instruction as described above.
The complete while loop for the fuzzy controller is shown in Listing Il.lc. The
functions get_position( ) and get_speed( ) are user-defined functions for reading the position
and speed of the ping-pong ball. The position can be measured using an ultrasonic
transducer. The speed can be computed by subtracting two successive position
measurements.
After calling fi//_weights(...) for both inputs, fire_ri/es(...), and calc_output(...), the
word adjust_motor(y) is a user-defined word that will set the speed of the fan according the
value of the output centroid y.
void fire_rules(unsigned char* inout_array, unsigned char* rules, unsigned char* out, int numout)
unsigned char calc_output(unsigned char* out, unsigned char* cent, int numout)
The three assembly language subroutines that are executed by these three C functions
are given in Listing 11.2. The subroutine /i//weights receives pointers to the weight and
membership arrays and the number of membership functions (5) on the stack and gets the
Input value x in accumulator B. After setting up these values as shownin Fig. 11.6, the
instruction MEMis called five times. Note that when MEMis called, the number of
membership functions is stored in 8 and the instruction dbne B.fiv/ will decrement B and
branch not equalto fw/, i.e., to the MEMinstruction.
The subroutine fire_ru/es shown in Listing 11.2 receives pointers to the inout, rules,
and out arrays on the stack and gets the number of outputs in accumulator D (B). The
Subroutine first clears the owt array and thensets up the registers for the REV instruction as
Shownin Fig. 11.8. |
The subroutine calc_oufput shown in Listing 11.2 receives pointers to the out and
cent arrays on the stack and gets the number of outputs in accumulator D (8). The
subroutine first sets up the registers for the JVAVinstruction, which expects index register X
to point to cent[k], index register Y to point to out[k], and accumulator B to contain the
158 Chapter11
number of output membership functions. Then the WAV instruction will compute a 24-bit
Value for numer (shown in Fig. 11.10) and store the result in Y:D and compute a 16-bit value
for denomand store this result in X. The WAV instruction is followed by the instruction
EDIV, whichwill leave the quotient (centroid value yg) in Y. This valueis transferred to D,
Which will return the 8-bit output in B to the C program.
; unsigned char calcoutput (unsigned char* out, unsigned char* cent, int
numout)
calc output: 7B = numout
ldx 7X -> cent array
ldy i -—* QUE array
wav
ediv ;Y = quotient
LEY ;D = quot A = OQ, B = output
rts
Fuzzy Control 159
PROBLEMS
11.1 Run the program in Listing Id using the simulator mode. Set a breakpoint atthefirst
get_slopes(...) function andsingle-step through the instructions of each function call.
Observethe contents of the inout_array[ ] and note howthe output centroid valueis
calculated.
a
11.2
a. Implement the floating ping-pong ball fuzzy control problem described in this
chapter. Use an ultrasonic transducer to measure the distanceto the ping-pongball.
The difference between two consecutive distance measurements can be used to
represent the ball speed. A muffin fan at the bottom of the Plexiglas cylinder is
used to maintain the ping-pong ball at a fixed height within the cylinder.
b. Add a dial that you canuseto set the height of the ping-pong ball.
c. Adda modethat has the ping-pong ball move between two different heights every
10 seconds.
d. Use a second ultrasonic transducer to measure the distance from the floor to your
hand. Usethe serial port to send this distance to your ping-pongball setup. Have
the ping-pongball float at the height of your hand abovethe floor. As you move
your hand up and down,the ping-pong ball should follow!
11.3 A fuzzy controller is used to maintain the idle speed of an automobile engine. Theidle
speed can be controlled by varying both the throttle position and the spark advance. In
this problem wewill consider only the throttle position. The two inputs will be
x1: the RPM error (current RPM - desired RPM)
x2: the change in RPM error from one measurementto the next
The output, y, will be a signal to a stepper motorthat changesthe throttle position. All
input and outputvalues are scaled from 0 - 255. The membership functions for the two
inputs are given in the following tables.
Input x1 ul u2 u3 ud |
NM_1 0 ) 20 100
NS_1 0 100 100 120
a 100 128 128 156
PS_1 136 156 156 255
PM_1 156 235 255 255
Input x2 ul u2 u3 ud
NS_2 0 0 64 128
L.6 64 128 128 192
PS_2 128 192 205 55
The output, y, will have the five centroid values, 10, 80, 128, 176, and 245,
corresponding toNM_y, NS_y, Z_y, PS_y, and PM_y.
a. Make plots of the membership functions for the two inputs, x/ and x2, and the
output, y. |
b. Makeup list of rules that seem sensible to you. For example, one rule might be
IF x/ is NS_J and x2 is Z_2 THEN yts PS_y
Make a fuzzy K-mapofyourrule set similarto the one shownin Fig. 11.9.
160 Chapter 11
c. Write a fuzzy control program for this problem by following the format in Listing
LL.1.
11.4 Suggest how you might design a fuzzy control system for each of the following
applications:
a. An auto-focusing camera.
b. The braking system ofa truck.
c. A washing machine
d. A rain-dependent variable-speed windshield wiper.
e. An electric oven.
f. Acceleration and deceleration control ofa train.
A robot manipulator.
ge
CodeWarrior Tutorial: DRAGON12-Plus-USB 161
Appendix A
CodeWarrior Tutorial
DRAGON(12-Plus-USB
1. Start the program by double-clicking the CodeWarrior IDE icon on the desktop.
2. When you run CodeWarrior V5.2 (IDE.exe) youget this startup dialog.
Projectname
Sted Using CodeWamor nin
Exampies!_4mcp
Location
D\LBE\Exemples!_4
oreit
GlobalAddressing
=
et fet=h1ent}
bes2 _
eet
ee iv Display on Startup HCS12X_LED
HCS12X_XGATE_cre
— HCS12X_XGATE_SéitwareReuuest
LEE_ORAGONIY
PagedCopydown
$12X_Automotye Custer Cel t
StringTatie . 1 =i); > name and location
XQaTeteks} Click Sev... to fill in file _—
* MC9S12 - where you want the project saved.
aP Cancel |
a
Bogen.’ XAeghguertwsnr.89 3B
a
Exampiert sme|
== fween'e $e Tern of eoery other pegeen! on o-emq diepiey
SSS
‘6 ha rm Seeson "RY a > C:actedea «dian? b> © cream Getines eet secrar ©
s.nciade metal erres . rive . esi bhp Dh inferazticn ©
3. Click + Sources entire
-
; Fame | eh Ont | Sager Poseses LIM IEPD LERTIA tee 2842"
oc. Pe tte CeeBOA Sineiucie ‘eetn_omn Bo oe jclertsce 42 the ereemhifp eatule ©
mower we tae a
at ee (2 (ent)
@ +4 Soares: 0 Co ce aos . oak tone Mi tote heve ©
Bow wh o Os a Pil neat) ort eysies ci ak (c3oqguersy to id Mita
= oat. oe 9 Co om t1wh - Owtt Peeve Gg coctt peat
° red 3 Occ ss DTG + Ont fae J 4@ evstpat
° omer t 9 O@eocg teen of “(Date Port F 38 oreesat
OL eee 3 Os +g O32 + Ours ensbio Let
eee 9 6 a OTP - Geta erah o al! F-eeyeen: do enlsos
4 Cj ehe Me 5 6 - a ta Hh ere > Ofte” let en! segeent ca * eg dlep are
esti 0 Oc om MeeTD -* Opty
‘ 7
“© west terecmr ©
‘ } ()
Double-click
‘ a fere
3
. peer
> Eidumane Cxtttes”
main.
‘ + a
to display Examplela
0 ies 0 °
4. Make sure HCS/2 Serial Monitoris selected. Click Make icon to compile program.
(To see this selection, you must have downloaded
The SMDriversfiles as described at the beginning
of this Appendix.)
Ri,tieeet au ay aye
Fle Ed® View Sead
Bioee eae
Examples1_4 mcp
aaenieemeneom eneraenren // Exasple la Turn cn every other sega
| @ HCS 12 Senal Morutor | my Fy ¢idclude t«hidef h> ve comaeia defan
/ Sinclude <«mc9sl2dg256 hos /@ derivot
Files | Link Order | Targets| fprogxe LINK_INFO DERIVATIVE *ec9sl2dg2
¢ Fie Code Data He Ak #include "main_asm h” ’@ interfece tot
B readme tt ra r/a a:
BD tos txt wa ne a void main(void) {
% ~(aSources 0 O- ea “¢ put your own code here #/
Q mon_asmh oO O° a PLL_anit(). // set eysten cloc
* & man asm 0 | DDRB = Oxff: // Port Bais cutp
< Q manc 0 O*--s DDRJ = Oxff: // Port J is outp
¥ B®) datapage c 0 | DDRP = Oxff. // Port P is outp
@ + (Startup Code 0 Qr- edd PTJ = 0x00. 7/ enable LED
* +COPim 0 Q- 2 PTP = 0x00. // enable all 7-s
+ (3 Linker Map 0 O- zi // turn on every other led ond segren
y + QLitvanes 0 O° +z = OxSS:
++ ©)
C) Debugges
Debugger Project
Crd Fies
Fie 0
0
Q-
0 >
a
a } for(:.) {} “* wait forever #*/
AACA
CodeWarrior Tutorial: DRAGON12-Plus-USB 163
BoesGuv«:
Examplet1_4 mcp |
———————
[@ WCSi2Seduoee ol BY BHR
// Exom
#includ
Sinclud
Flee | Lek Cron | Targets | Spragaa
« Fle Ce Os&S #includ
BD tcatrate va nae a:
B tom we var os void 9
43 Scaces ax Baees 7* pu
D mensmh 0 Oe a FLI_1
D rman om 1S Be es CDRS
Dom: 24 Oe © sf DUR
BD dvxemes 15 Oe eg OLRE
6. Click the Start/Continue (F5) icon to run the program. Every other LED and
segments a, c, e, and g ofall
7-segmentdisplays should belit.
eeePh weiie
se wae 85
“CALUE\Examien\Evamoles! _4s\Souces\manc Line10
“
/© Bat your oan code hece #/ An
TRETIIITE, Use RORoe CO4S LDD #22015
DORE» Oxtt; fs Poxt Bis outpuc CO4B STAB 0x03
DDRI » Oxtt; 7¢ Pore J as output CO4D STAB Ox026A ¥
DORP = Oxtt; 4’ Pare Pia output
OTT = Avan.
a
1+ amente TED a TNS Gees » baits eri... rl in} rth
c te ? Iv Q
erat pron
een, —* . > f CL aS TS em
Mreliseske
(the LEDs and
Se a ed
7-segment displays
should go out)
: arin eT é peat
ota pceSofa aT)tii
ope ae e
te
} feat pig eet
8. Now movetheslide.
switch up to the
RUN mode
9. Press the reset button again. Your program 10. Nowmovetheslide switch
is running in flash memory on the board right back to the LOAD mode
and the LEDs and 7-segmentdisplays and press the reset button. The
should come back on. Serial Monitoris running on
the board (the 7-segment
display will go out) and you
are ready to download a new
: ;
im) ie) ee) _lelzisielj oI ; anemic } a ce CRA Fa tag yl
PV EL BIBSATES
ae‘fe
mone | Auto . Symb Geb '
~ @ _PPPAB <2 volatile DDRABSTR ® o000e0 oo 00 00 20... 8
W _DDRI <l> volatale DDRISTR goooca 05 00 00 OV .... ¥
BLY Sores ‘SaaS E TREESee erp eRoo SORER
t oa eee ate ee ae actekide nite aekhaes mie . Pe ae SS
= man ”
im 2
?
For Helo, press Fl 99C9S120G256B Ereskpoirt
CodeWarrior Tutorial: DRAGON12-Plus-USB 165
At this point you could create a completely new project by following steps 2 and 3 above and
edit main.c to be some new program. Instead wewill keep the same project and just keep
changing main.c to be Example] b — Example4b.
12. Select File > Open... Locate and openthe file Copy ofExample 1b main.c.
Ae
I Project Processus Lpert Werke et
Open Peat
uw
ee
13. Selectall of Copy ofExample 1b 14. Select all of main.c and paste
main.c and copyit to the clipboard. Copy of Example 1b main.cin its place
Oe eed
2 tes x x
¢ >
166 Appendix A
Appendix B
The programming model of the HCS12 is identical with that of the 68HC11 and
68HC12. It consists of the set of registers shown in Figure B.1. Wewill refer to these as the
CPU12 registers. The HCS12 also contains a register block that is associated with the
variousI/O operations of the HCS12.
In this section we will describe the CPU12 registers and illustrate howdata can be
moved into and out of these registers using some of the HCS12 instructions and addressing
modes.
Accumulator A Accumulator 6
Accumulator D
Inde x register X IX
Inde x register Y IY
Stack Pointer SP
Program counter PC
js x HIN Condition
rhhan
code
cacar
regists
~cfor
The HCS12 has two 8-bit accumulators, A and B, that can be combined into the
single 16-bit accumulator D. That is, A is the upper 8-bits of D andB is the lower8-bits of
D. The accumulators are used for storing intermediate results and for performing arithmetic
and logical operations. The following are some ofthe instructions involving accumulators
A, B, and D.
168 Appendix B
CBA Compare A to B
CMPA Compare A to memory
CMPB Compare B to memory
CPD Compare D to memory
TSTA Test A for zero or minus
TSTB Test B for zero or minus
Stacking Instructions:
PSHA Push A
PSHB Push B
PSHD Push D
PULA Pull A
PULB Pull B
PULD Pull D
The index registers X and Y are 16-bit registers that are used for several different
purposes, They can be used in a mannersimilar to the accumulators for temporary storage
When moving 16-bit data to and from memory. The following are some ofthe instructions
involving index registers X and Y.
Stacking Instructions:
PSHX Push X
PSHY Push Y
PULX Pull X
PULY Pull Y
The main use of the index registers X and Y is in conjunction with various modes of
addressing. An addressing mode is what specifies where a particular data item is to be
found. For example, the instruction LDAA #$10 is an example of the immediate addressing
mode. This meansthat the data $10 immediately follows the opcode in memory. The$ sign
means that 10 is a hexadecimal value. The # sign meansthat it is immediate addressing.
One of the most important addressing modes associated with the index registers XY and is
indexed addressing. For example, the instruction
HCS12 Assembly Language Essentials 171
LDAA 0,X
meansload into accumulator A the byte in memory at the address that is in index register YX.
Wesay that X is pointing to a byte in memory. The zero in the above instruction is a
displacement that gets added to the value of X. For example, the instruction LDD 4,X will
load two bytes of memory into accumulator D (A:B). The byte at address X+4will be loaded
into accumulator A and the byte at address X+5 will be loaded into accumulator B.
The stack is a region of memory that is set aside for storing temporary data. The
stack pointer, SP, is a 16-bit register that contains the address of the top of the stack. The
stack is used by the HCS12 to save the return address when a subroutine is called. It is also
used to save register values whenaninterrupt occurs.
The stack can be used to save the contents of registers A, B, X, and Yusing the
instructions PSHA, PSHB, PSHX, and PSHY. These values are removed from the stack
using the instructions PULA, PULB, PULX, and PULY.
The following are someof the instructions involving the stack pointer, SP.
SS
TXS Transfer X to SP
TYS Transfer Y to SP
‘
EXG X,SP__ Exchange X and SP
=?
Decrement and Increment Instructions:
DES Decrement SP
INS Increment SP
CompareandTest Instructions:
CPS Compare SP to memory
CPY Compare Y to memory
172 Appendix B
The program counter, PC, is a 16-bit register that contains the address of the next
instruction to be executed. When an instruction is executed the program counter is
automatically incremented the number of times needed to point to the next instruction.
HCS12 instructions may be from one to six bytes long. Therefore, the program counter may
be incrementedby | to 6 depending upontheinstruction being executed.
Someinstructions cause the program counter to change to some new valuerather than
simply be incremented. These include the branching, jump, and subroutine instructions. We
will discuss subroutines in Section B.3 and branching instructions in Section B.4.
The HCS12 has a condition code register (CCR) that contains five status flags or
condition codes andthree control flags. The five status flags are the carry flag (C) , the zero
flag (Z), the negative flag (N), the overflow flag (V), and the half carry flag (H). The three
control flags are the interrupt mask flag (/), the X-interrupt mask flag (A), and the stop
disable flag (S). Each flag is one bit in the condition code register. The location of eachflag
is shownin Figure B.2.
765 4 3 2 1 0
s[xfu[z[x[z]v[c
Stop disable —— —— Cary
X - Interrupt mask —— Overflow
Haf carry —— Zen
Interrupt mask ——— Negative
Anyof the bits in the condition code register (with the exception of the X bit) can be
set using the HCS12 instruction ORCC. This instruction will perform a logical OR ofthe
CCR with a byte mask in memory (immediate addressing) containing a 1 in the bit locations
to be set. Any of the bits in the condition code register can be cleared using the HCS12
instruction ANDCC. This instruction will perform a logical AND of the CCR with a byte
mask in a memory containing a 0 in the bit locations to becleared.
Wewill nowlook at the meaning of each bit in the condition coderegister.
Carry(C)
The carry flag is bit 0 of the condition code register. It can be considered to be an
extension of a register, or memory location operated on by an instruction. Thecarry bit 1s
changedby three different types of instructions. Thefirst are arithmetic instructions. These
include the addition instructions ADDA, ADDB, ADDD, ADCA (addwith carry), ADCB,and
ABA (add B to A), the subtraction instructions SUBA, SUBB, SUBD, SBCA (subtract with
carry from A), SBCB, and SBA (subtract B from A), and the compare instructions CMPA,
CMPB, CBA, CPD, CPX, and CPY. Thecarry bit is also changed by the multiplication
HCS12 Assembly Language Essentials 173
instructions, MUL, EMUL, and EMULS,thefive division instructions, JD/V, JDIVS, EDIV,
EDIVS, and FDIV, the negate instructions, NEG, NEGA, and NEGB,and the decimal adjust
instruction, DAA.
The second group of instructions that can change the carry bit are the shifting and
rotating instructions such as ASL, ASLA, ASLB, ASR, ASRA ASRB, LSL, LSLA, LSLB, LSR,
LSRA, LSRB, LSRD, ROL, ROLA, ROLB, ROR, RORA, and RORB.
Finally, the carry bit can be set to 1 with the instruction SEC(set carry), and cleared
to zero with the instruction CLC (clear carry). These instructions which are valid for the
68HC11 get translated to the HCS12 instructions ORCC #$01 and ANDCC #8FE
respectively.
Addressing modes determine the address where the data associated with instructions
are located. This addressis called the effective address. All of the HCS12 addressing modes
are listed in Table B.1. Only thefirst six addressing modes in Table B.1 are available on the
68HCI1. (On the 68HC11 the relative addressing and indexed addressing use only 8-bit
offsets.)
The 68HC12 and HCS12 have added the seven new addressing modes shown in the
bottom half of Table B.1. In addition, the 68HC12 and HCS12 allow 5-bit, 9-bit, and 16-bit
constant offsets in the indexed addressing mode. The constant offset is added to xX, Y, SP, or
PC to compute the effective address. For example, if X contains the value $1234, then the
instruction LDD -2,X will store the value in D at address $1234 minus 2 or $1232. This will
store the contents of A at $1232 and the contents of B at $1233. Similarly, the instruction
JSR 0,¥ will jumpto a subroutine at the address stored in Y.
The pre-decrement indexed addressing mode computes the effective address byfirst
decrementing X, Y, or SP by a value of 1 to 8. For example, if X contains the value $1234
then the instruction STAA /,-X will first decrement X by 1 to $1233 and then store the value
of A at address $1233.
As a second example, consider the MOVWinstruction whichis of the form
MOVW source,dest
and moves a word (16-bits) from the effective address source to the effective address dest.
(There is also a MOVBinstruction which movesan 8-bit byte.) The addressing mode for
source can be either immediate, extended, or indexed, and the addressing mode for dest can
be either extended or indexed.
For example, suppose that X contains the value $1234 and the word $5678 is stored at
I< - ——
address $1234 as shownin Figure B.3. Then after executing the instruction MOVW0,X,2,-X
oO
175
HCS12 Assembly Language Essentials
the value at address $1234 (0,X) will be copied to address $1232 (2.-X) and X will nowbe
equal to $1232 as shown in Figure B.3.
Before After
78 78
MOVW 0,X%,2 eh
The pre-increment indexed addressing mode computes the effective address byfirst
incrementing X, Y, or SP by a value of 1 to 8. For example, if Y contains the value $1234
then the instruction LDAB /,+Y will first increment Y by 1 to $1235 and thenstore the value
of B at address $1235.
The post-decrement indexed addressing mode computes the effective address by
using the value in X, Y, or SP (equivalent to 0,X, for example) and then decrementing_X,Y,
or SP by a value of | to 8. For example, if X contains the value $1234 then the instruction
STD 2,X- will store the value of D at address $1234 and then decrement.X by 2 to $1232.
The post-increment indexed addressing mode computesthe effective address by using
the value in X, Y, or SP (equivalent to 0,X, for example) and then incrementing X, Y, or SP
by a value of | to 8. For example, if X contains the value $1234 then the instruction LDD
2,\'+ will load the value at address $1234 into D and then increment_Xby 2 to $1236.
The accumulator offset indexed addressing mode computesthe effective address by
adding the value of A, B, or D to X, Y, SP, or PC. For example, if X contains the value
$1234 and B contains the value $12 then the instruction ADDA B,Xwill add the byte at
address $1246 (X+B)to the value in 4 and leave the sum in A.
The 16-bit constant offset indexed indirect addressing mode adds a 16-bit constant
offset to X, Y, SP, or PC to compute the address that contains the effective address. For
example, if Y contains the value $1234 and if the value $5678is stored at address $1234 then
the instruction LDAA [0,¥] will load A with the byte at address $5678.
The D accumulator offset indexed indirect addressing mode addsthe value in D to_.X,
Y, SP, or PC to compute the address that contains the effective address. For example,
consider the instruction ADDA [D,X]. If X contains the value $1234 and D contains the
value $2345, then the value at $3579 ($1234+$2345) will contain the address of the byte that
is added to accumulator A.
B.3.2 Subroutines
subroutine (RTS) instruction. This will cause the program to return to the instruction
following the JSR or BSR instruction that called the subroutine.”
The computer knows where to go when an R7S instruction is executed becauseit
stored the return address on the stack when the JSR or BSR instruction was executed. The
RTSinstruction just pops the value ontop of the stack into the program counter.
The BSR instruction uses relative addressing. It is a 2-byte instruction in which the
second byte is a relative (two's complement) offset that gets added to the address of the next
instruction to compute the subroutine address. This means that the address of the subroutine
must be within +128 bytes from the location of the BSR call. As a result the BSR instruction
can only be used whencalling nearby subroutines.
Several different addressing modes can be used with the JSR instruction. In the
extended addressing mode the 16-bit address of the subroutine follows the opcode. For
example, if in an assembly language program the label of a subroutine is SUBJ, then the
instruction JSR SUB/ will be assembled using this extended addressing mode in which the
address of SUB/ will follow the JSR opcode.
As another example, if you want to jump to a subroutine whose addressis in index
register Y, you can execute the instruction JSR 0,Y. This is an indexed addressing mode in
whichthe effective address is computed by adding a signed 5-bit offset (0 in this case) to the
value in indexregister Y.
A program segment that calls a subroutine may be using a certain register, say B, for
a particular purpose, such as a counter. If the subroutine changes the value of B then an error
will occur in the calling program. To prevent this from happening a subroutine should save
the values of registers that it modifies by pushing them onthe stack at the beginning ofthe
subroutine. Then they must be popped from the stack, in reverse order, at the end of the
subroutine before the RTS instruction is executed as shownin Fig. B.5.
; subroutine example
subname
pshb ; save B
pshx ; save X
instructions that
; change B and X
pulx ; restore X
puxb + restore B
rts
It is important to remember that the return address is on the stack and this is the
address that is popped by the RTS instruction. Therefore, the same number of bytes must be
popped from the stack at the end of a subroutine as were pushed onto the stack at the
beginning of the subroutine.
* The 68HC12 also has a CALLinstruction that calls a subroutine in expanded memory. To return from such
a subroutine one uses the RTC (return fromcall) instruction.
HCS12 Assembly Language Essentials 179
The HCS12 has a large number of branch instructions. There are two major
categories, short branch and long branch instructions. When writing assembly language
programs you would normally use the short branch instructions. We will first look at short
conditional branch instructions that test only a single bit in the condition code register. We
will then explore the difference between unsigned and signed branch instructions.
The instructions shown in Table B.3 test the state of one of the flags in the condition
code register. Other branchinginstructions, that will be describedin later sections, test some
combination ofthe status flags.
A branching instruction will cause a branch to occur if the branch test is trve. For
example, the branching instruction BEQ (branch if equal zero) will cause a branch in the
program if the Z flag in the condition code register is 1. This will be the case if the result of
the previousinstruction produced a result of zero.
The conditional branch instructions shownin Table B.3 are all two bytes long. The
first byte is the opcode and the second byte is the relative displacement of the branch
destination. This is the two's complement number that must be added to the value ofthe
program counter + 2 (the address of the next instruction) to obtain the address of the
instruction to be executed if the branch test is true. If the branch test is fa/se, then the
instruction following the branchinstruction is executed. This ts illustrated in Fig. B.6. Note
that if Z = 1 when the BEO instruction is executed, the program will branch to the address
formed by adding the displacement (06) to the address of the next instruction (5014); that is,
to address 501A = 5014 + 06.
180 Appendix B
Ly = 5014 + 06 = 501A
Figure B.6 The displacement (06) in a branchinstruction is added to the program counter + 2
to obtain the destination address of the branch.
Figure B.7 Negative branches can be found by subtracting the addressof the
next instruction from the destination address
Note that since the branch displacement for the short branch instructions given in
Table B.3 is a single 8-bit byte, a short branch instruction can only branch forward a
maximumof 127 bytes ($7F) and backward a maximum of -128 bytes ($80). The counting
of these bytes always begins at the address of the instruction fo//owing the branchinstruction.
Atfirst this may seem like a serious limitation. Actually it is not. In fact, the 68HCI1 has
only short branch instructions. Well written assembly language programs should not need to
branch conditionally more than +127 bytes. Branches should take place within small
program segments or subroutines. If you need to perform lots of instructions within a loop,
for example, you should use subroutine calls to make the program more modular. This will
make the program mucheasier to debug and maintain.
In the unlikely event that you do need to branch conditionally more than +127 bytes
you can use the HCS12 long branchinstructions. Each of the instructions in Table B.3 has a
long branch equivalent. For example, the instruction LBEQ (long branchif equal to zero)
uses the test Z = 1 just as the instruction BEQ does. However, this instruction uses a 16-bit
signed displacement rather than an 8-bit displacement. This means that the branching
displacement can range from -32,769 ($8000) to +32,767 (S7FFF). The opcodes for the long
branch instructions are two bytes long so that these long branch instructions use a total of
four bytes and execute slower than the short branchinstructions.
Both the short and long branch afvays instructions in Table B.4 use a relative
displacementin the instruction. Since this is the numberthat is added to the address ofthe
following instruction, it is independent ofthe destination offset address. This means that if
the entire program is moved within the memory, this relative displacement does not change.
The use of relative displacements for determining a destination address will allow you to
Write position-independent code. This meansthat a program can be moved to anylocation in
memory andstill run.
The JMP instruction shown in Table B.2 will jump uncondionally to the effective
address determined by the particular addressing mode usedin the operand. This could be an
absolute address (extended addressing) or some formof indexed addressing,
182 Appendix B
The conditional branch instructions given in Table B.3 are the ones most commonly
used, In fact, you can write any program using only these. However, sometimes it is
convenient to use the additional conditional branch instructions given in Tables B.5 and B.6.
You must, however, be careful. It 1s very easy to make a mistake when using these
conditional branch instructions. The instructions in Table B.S must only be used when you
are thinking about wasigned numbers; that is, 8-bit numbers with decimal values between 0
and 255 ($00-$FF), or 16-bit numbers with decimal values between 0 and 65,535 ($0000-
SFFFF).
The branching instructions in Table B.6 must only be used when youare thinking
about signed numbers; that is, 8-bit signed numbers with decimal values between —128 (S80)
and +127, ($7F), or 16-bit signed numbers with decimal values between —32,768 ($8000)
and +32,767 ($7FFF).
It is very easy to confuse the instructions in Tables B.S and B.6. This can lead to
execution errors that are sometimes hard to find. For example, suppose accumulator B is
used as a counter and you want to go through a loop 20019 ($C8) times. You might think
that the loop shownin Fig. B.8 will work.
CLRB 7;set B = 0
LOOP INCB ;increment B
CMPB #SC8 ;compare B to C8H
BLT LOOP sloop if B < 200
It won't! The branching instruction BLT LOOP will fail the first time. This is
because the value of B is 1 and the value of $C8 is not 20019 but is —5610._ Rememberthat
the BLT instruction (and all the instructions in Table B.6) consider all numbers to be two's
complement signed numbers. Inasmuch as | (the value of B) is greater than —56)9 the
instruction BLT will not branch.
Theinstruction you really want to use is BLO (branch if lower). This instruction, and
all instructions in Table B.5 treat all numbers as unsigned numbers, so that $C8 is considered
to be 200)9 and not —56)0.
In Table B.5 note that the instructions BHS and BLO test only the carry flag and are
the same as BCC and BCSrespectively. All other instructions in Tables B.5 and B.6 use
branchtests that involve more than one flag in the condition coderegister.
The HCS12 has two bit-condition branch instructions that are given in Table B.7.
The BRCLRinstruction will branch if selected bits in a particular memory location are zero.
The bits are selected by setting correspondingbits in a mask to |. The BRCLR instruction
will then branch if the logical AND of the memory location with the mask is zero. The
general form ofthe instruction is
BRCLR opr,msk,rel
Where opr is the addressing mode for the memory location to be tested, msk is the mask
value, and re/ is the label of the branch destination address.
The BRSET instruction shown in Table B.7 will branch if selected bits in a particular
memory location are 1. In this case the BRSETinstruction will branch if the logical AND of
the one's complement of the memory location with the maskis zero.
The HCS12 has two decrement and branchinstructions that are given in Table B.8.
The DBEQinstruction will decrement a counter (A, B, D, X, Y, or SP) and branchif the
Counter is equal to zero. The DBNEinstruction will decrement a counter (A, B, D, X, Y, or
SP) andbranchif the counteris not equal to zero. As an example the instructions
are used in the fuzzy control function fillweights(...) in main.asm to execute the MEM
struction B times.
184 Appendix B
There are six HCS12 non-maskable interrupts shown in Table B.9. Eachinterrupt
source has a 16-bit vector address that holds the address (interrupt vector) of the code to be
executed when the interrupt occurs.
The non-maskable interrupts shown in Table B.9 are listed in order of priority. If
more than one interrupt occurs at the same time, the interrupt with the highest priority will
be servicedfirst. We will briefly describe each of these non-maskable interrupts in turn.
Reset
When the RESET pin on a HCS12 goes low, normal microprocessor functions are
suspended. When this pin returns high the microprocessor will set bits X and / in the
condition code register and start executing instructions starting at the address stored at
SFFFE-SFFFF. The HCS12 has a power-on reset (POR) circuit that causes the reset signal to
be asserted internally after power has been applied to the processor.
It is necessary for addresses $FFFE-SFFFF to be in some type of non-volatile
memory (ROM, EPROM, or Flash memory) so that a valid reset vector will be at that
address. Of course, the memory it points to must also be in non-volatile memory so that
some meaningful code will be executed when youturn on the processor.
HCS12 Assembly Language Essentials 185
Whenthe R7/ instruction is executed the return address on the stack is popped into
the program counter. The programwill therefore continueat the pointin the program where
the interrupt occurred. For a software interrupt this would be the statement following the
SWI instruction.
all registers shownin Fig. B.9 are pushedonthe stack, both the / bit and the Xbit in the CCR
are set. This means that another X/RQ interrupt cannot occur during the execution of an
A7RQinterrupt service routine. Executing the R77 instruction at the end of the interrupt
service routine will pop the registers shown in Figure B.9 off the stack, including the CCR
register which will have its X bit cleared. At that point a new X/RQ interrupt can occur.
All of the interrupts shown in Appendix D with vector numbers between 6 and 57 are
maskable interrupts that are inhibited when the / bit in the CCR is set (using the SE/
instruction). To enable all of these maskable interrupts you must clear the / bit in the CCR
by executing the CZ/ instruction. In addition, each interrupt source will have a local enable
bit that must be set in one of the I/O registers in order to enable that particular interrupt. For
example, to enable pin PE/ as an IRQ interrupt, you must set bit JROEN (bit 6) in the
Interrupt Control Register, INTCR.
The interrupts shown in Appendix arelisted in order of priority. If more than one
interrupt occurs at the same time, the interrupt with the highest priority will be servicedfirst.
When maskable interrupts occur the same process of pushing the registers on the
stack occurs as described above for software interrupts.
Summary of C Function Calls to main.asm 187
Sr
Appendix C
Seee eee
void £fill_weights (unsigned char* weight, Given a crisp input x and membership
unsigned char* membx, int nummem_fncs, functions, membx,fill the corresponding weight
char x); array. el
void fire_rules(unsigned char* inout_array, Given inout_array containing weight arrays and
unsigned char* rules, unsigned char* out, a set of rules,fire all rules andfill the out array.
int numout) >; et
unsigned char calc output(unsigned char* Calculate crisp output given the out array and
out, unsigned char* cent, int numout) ; the output membership singletons, cent. gee
191
MC9S12DP256BInterrupt Vectors
Appendix D
TimerChannel 3 SFFES-SFFE9
Timer Channel 4 SFFE6-SFFE7
Timer Channel 5 SFFE4-SFFE5
Timer Channel 6 SFFE2-SFFE3
Timer Channel 7 SFFEO-SFFE1
Timer Overflow SFFDE-SFFDF
Pulse Accumulator A Overflow SFFDC-SFFDD
Pulse Accumulator Input Edge SFFDA-SFFDB
SPIO SFFD8-SFFD9
SCI 0 SFFD6-SFFD?
SCI 1 SFFD4-SFFD5
ATDO SFFD2-SFFD3
ATD1 SFFDO-SFFD1
vo} Go| Ga] Co] Go] po} po] po] pO] py] po] po]
Port J SFFCE-SFFCF
Port H SFFCC-SFFCD
Modulus Down Counter Underflow SFFCA-SFFCB
Pulse Accumulator B Overllow SFFCS-SFFC9
CRG PLL lock SFFCO-SFFC?
CRG Self Clock Mode SFFC4I-SFFCS5S
BDLC SFFC2-SFFC3
IC Bus SFFCO-SFFC1
SPT1 SEFBE-SFFBF
SPl2 SFFBC-SFFBD
EEPROM SFFBA-S$FFBB
35 FLASH SFFBS-SFFB9
36 CANO wake-up SFFB6-SFFB7
nay CANOerrors SFFB4-SFFBS
CANO receive SFFB2-$FFB3
192 Appendix D
Appendix E
Fuzzy logic has been applied successfully to a wide variety ofdifficult control
problems. The input and output control variables are members of fuzzy sets that admit
varying degrees of membership. In this appendix we will introduce the basic ideas of
fuzzy sets. We will then describe how a fuzzy controller works and present an overall
approach to implementing a fuzzy controller on a microcontroller. The HCS12 has some
special instructions which simplify the implementation of a fuzzy controller. We define
some C functions in Example 20 that use these HCS12 instructions to implement a fuzzy
controller.
Lotfi Zadeh introduced the term fuzzy sets in 1965." In normal "crisp" logic the
basic assumption is that assertions, or statements, are either true or false. But this
assumption leads to paradoxes. For example, is the sentence in Figure E.| true or false?
If the sentence is true, then it must be false; but if it is false, then it must be true. There
are many such paradoxes in whichit appears that true must be equal to false.
Fuzzy logic does not require that everything beeither true or false. In normal
"crisp" set theory an element either belongs to the set, or it doesn't. However, a little
reflection should convince youthat most things in the world aren't that black and white.
For example, is a given person young’? We can consider young to be a fuzzy set in which
* L. Zadeh, "Fuzzy Sets," Inform. and Contr., Vol. 8, pp. 338-353, 1965.
194 Appendix E
Young
at a2 Age
When applied to fuzzy sets the logic operations NOT, AND, and OR are defined
as shown in Table E.1. These are not the only possible definitions but they are the ones
most commonly used. Note that they reduce to the crisp case when A and B havethe
binary values 0 and 1.
If we apply the NOT operation to the fuzzy set Young we obtain the fuzzy set
NOT Young shownin Figure E.3. In a similar way we could define the fuzzy sets Old
and NOT Old as shownin Figure E.4.
al a2 Age
Age
Figure E.4 Membershipfunctions for the fuzzy sets Old and NOT Old
Wecan use the AND operation given in Table E.1 to define newfuzzysets. For
example, we might define Middle Age as NOT Young AND Not Old. Taking the
minimum of the membership functions for VOT Young and NOT Oldwill produce the
membership function for Middle Age as showninFigure E.5.
7
NOT Young, NOT Old
4
4
(a) Age
(o) Age
Figure E.5 Deriving the membershipfunction for Middle Age
COLD, COOL,MEDIUM, WARM, and HOT. The shape of the membership functions
are, in general, trapezoids that may have notop (triangles) or may havevertical sides as
shownin Figure E.6.
NM NS Z PS PM
ol 1 l | |
0 36 82 128 174 220 255
Universe of discourse
INPUTS
14
Map to Fuzzy Sets get_inputs();
--_—_—_- —————— rrSSS st—CS
FUZZY RULES
fire_rules();
lf AAND B then L
Defuzzification find_output();
OUTPUT
The fuzzy controller itself consists of a set of fuzzy rules of the form ifx; is PM
ANDx2 is Z, then y is NM, where x; and x2 are inputs, yis the output, and PM, Z and NM
are fuzzy sets of the type shown in Figure E.6. For example, a fuzzy rule for an air
conditioner might be if temperature is WARM ANDchangein temperature is ZERO, then
wet
eeeeserey
Introduction to Fuzzy Control 197
motor speed is FAST. Note that WARM,ZERO, and FASTare fuzzy sets. After applying
all of the fuzzy rules to a givenset of input variables, the output (motor speed in this
case) will, in general, belong to more than one fuzzy set with different weights. The
weighted output fuzzy sets are combined in a mannerto be described below and then a
centroid defuzzification process is used to obtain a single crisp output value.
The fuzzy controller shown in Figure E.7 consists of three parts: the fuzzification
of inputs, the processing of rules, and the defuzzification of the output. The overall
algorithm of a fuzzy controller is shown in Figure E.8 where each function represents the
three parts of the controller shown in Figure E.7. Wewill consider each of these parts
separately.
doforever
{
getinputs();
firerules();
find_output() s
For each crisp input x; a set of weights w;! are computed for each membership
function such as those shown in Figure E.6. In general, each input will have a different
set and number of membership functions. For input number j the weight 1’ can be
stored in a vector weight;{j], j = 1, M; where ©; is the number of membership functions
for input i. Each value in the weight vector is a weight value between 0 and | given by
the shape of a particular membership function. Typically for a given input the weight
vector will contain up to two non-zero entries in adjacent cells.
The purposeof the function get_inputs() is to read each input value x; and fill the
weight vector weight/M;/ with the degree of membership of x; in each input fuzzyset.
The pseudo-code for get_inputs() is shown in Figure E.9. In this figure 4; is the number
of membership functions for input/.
get_inputs()
for i = 1, num_inputs
{
get_x(1);
fill_weight(xj;, Mj);
}
The function get_x(i) is problem dependent andwill consist of reading the input
values xj, i= 1, num_inputs. The function fill_weight(x;, Mj) will fill the weight vector
weight[Mj] with the degree of membership ofx; in each ofthe 4/4; membership functions
for input /.
198 Appendix E
In this representation of the problem, 4), 42, B;, B2, A’, and B' are input fuzzy
sets and L;, £2, and L' are output fuzzy sets. Fuzzy reasoning would form the union of
the intersection of 4’ and A). This is interpreted as being the maximum(union) ofthe
minimum (intersection) of the membership functions A‘ and Aj. In Figure E.10 A’ ts
taken to be the singleton fuzzy set.x; = a. In rule 1, the maximumof the intersection
(minimum) ofthis singleton with 4] 1s the value wy shown in Figure E.10. Similarly, the
maximumofthe intersection (minimum) ofthe singleton x2 = 4 with By is the value w2
shown in Figure E.10. The fact xy = @ and x> = b applied to the antecedent x; is Ay and
x? iy By; is interpreted as the intersection (minimum) ofwy and w, ie. 2 for rule 1 in
Figure E.10. The conclusion of rule 1, y is Ly, is found by taking the intersection (T-
norm) of w2 with L). This is normally the minimumoperation which would truncate £7
to the height w2. However, for fuzzy control it is sometimes advantageous to use a
product T-normfor this intersection which would havethe effect of multiplying L; by w2
as shownin rule | in Figure E.10. Thus, rule | in Figure E.10 will contribute the fuzzy
set w2*L, to the conclusion fuzzy set L'. Similarly, rule 2 in Figure E.10 will contribute
the fuzzy set w/*ZL> to the conclusion fuzzy set L’ because wy is the minimum of wy and
w2 for rule 2. Note that if ZL) and L> are singletons (as is normally the case) then there
will be no difference in using the minimumT-normorthe product T-norm.
The conclusion fuzzy set L' is found by forming the T-conorm of w2*Z; and
wy*L>. This is normally the maximumoperation. However, sometimesbetter results are
obtained bytaking the sum of w2*Z; and w/*Z2 as shownin Figure E.10. The difference
between these two approachesis shown in Figure E.11.
Introduction to Fuzzy Control 199
m L1
tule 4
—>-
yO - w2*L1
ut A2 B2 tule2 L2
—>>
oa ae -- wi*l2
y
y sum
{?
yO y
Figure E.10 Fuzzy inference
2 see a ELS
m m m
” |?
bA
yO y yO y yO y
Maximum Sum Singletons
Figure E.11 Comparing the MAX rule and the SUM rule
If L; and L2 are singletons (the normal case) then taking the maximum or sum of
the two rules shownin Figure E.10 will be the same as shown in Figure E.11. [In general,
they won't be the same if more than one rule contribute to the same output fuzzy set Lj.
In this case the maximum rule will keep only the maximum value while the sumrule will
add the contributions of each.
The conclusion output L’ is a fuzzy set shown by the bold line membership
function in Figures E.10 and E.11. To obtain a crisp output sometype ofdefuzzification
process is required. The most common methodis to compute the centroid ofthe area of
L'. We will see that using the sum rule will be helpful in analyzing centroid
defuzzification in Section E.5.
200 Appendix E
The last step in the fuzzy controller shown in Figure E.7 is defuzzification. This
involves finding the centroid of the net output fuzzy set L‘ shown in Figures E.10 and
E.11. Although we have used the MIN-MAXrule in the previous section wewill begin
by deriving the centroid equation for the sum rule shown in Figure E.11. This will
illuminate the assumptions made in deriving the defuzzification equation that we will
actually use in the fuzzy controller.
Let L(y) be the original output membership function associated with rule i where
y is the output universe of discourse (see Figure E.10.). After applying rule i this
membership function will be reduced to the value
where wj is the minimum weight found by applying rule i. The sum of these reduced
output membership functions overall rules is then given by
N
Mi) = Ymi(y) (E.2)
i=]
fvM(ydy
yo = (E.3)
fM(v)dy
fvLi(vidy
Cj (E.4)
- fLiddy
But
l= {Liddy (E.5)
is just the area of membership function L;(). Substituting (E.5) into (E.4) we can write
Using Eqs. (E.1) and (E.2) we can write the numeratorof (E.3) as
Introduction to Fuzzy Control 201
N
fyMO)ady = |y UwiLi(y) dy
i=/
N
= vsywjLj(y) dy
i=]
N
= > wicjl; (E.7)
i=]
= >JSwiLj(y) dy
i=]
N
= > wil; (E.8)
i=]
where (E.5) was used in the last step. Substituting (E.7) and (E.8) into (E.3) we can
write the crisp output of the fuzzy controller as
N
» wicilj
i=/ -
yo="N 7]
wylj
j=]
Eq. (E.9) says that we can compute the output centroid from the centroids, cj, ofthe
individual output membership functions.
Note in Eq. (E.9) the summation is over all NV rules. But the number ofoutput
membership functions, Q, will, in general, be less than the number ofrules, V. This
meansthat in the sums in Eq. (E.9) there will be many terms that will have the same
values of c; and J;. For example, suppose that rules 2, 3, and 4 in the sumall have the
output membership function L‘as the consequent. This meansthat in the sum
the values c; and /; are the same values ck and /‘ because they are just the centroid and
area of the k/” output membership function. These three terms would then contribute the
value
Wk = (w2 + w3 + w4)
is the sum ofall weights from rules whose consequent is output membership function ZL‘.
This meansthat the equation for the output value, yg, given by (E.9) can be rewritten as
Q
> Wickjk
k=]
>, Wik
k=]
If the area ofall output membership functions, /‘ are equal, then Eq. (E.10) reduces to
Q
» Wkek
_A=l
V0 Q (E.11)
2,
k=]
Eqs. (E.10) and (E.11) show that the output crisp value of a fuzzy controller can be
computed by summing over only the number of output membership functions rather than
overall fuzzy rules. Also, if we use Eq. (E.11) to compute the output crisp value, then
we need to specify only the centroids, ck, of the output fuzzy membership functions.
This is equivalent to assuming singleton fuzzy sets for the output.
For more information on fuzzy control you can consult the many booksonthis
topic including
Appendix F
A phase locked loop (PLL) is used to produce a higher output frequency than the
oscillator frequency generated by the crystal on the board. A block diagram of the PLL that
is in the Freescale MC9S12DG256 microcontroller is shown in Fig. F.1. The registers used
to program this phase locked loop are shown in Fig. F.2. The lower four bits of the REFDV
register are used to divide the oscillator frequency, OSCCLK, by a value from | to 16,
producing a signal with frequency /|. The phase difference betweenthis signal and a second
signal with frequency f2 is measured by the phase detector. The loop filter converts this
phase difference into an analog voltage that is the input to a voltage controlled oscillator
(VCO). The output of the VCO is a clock signal with a frequencythat is proportional to the
input voltage to the VCO. If the input voltage to the VCO increases, the output frequency
will increase. If the input voltage to the VCO decreases, the output frequencywill decrease.
If you want the output frequency f, to be N times the reference frequency/|, then the loop
programmable divider will divide the output frequency /; by NVto produce the frequency/),
which will end up being the same frequency as fi. [ff starts out at a lower frequencythan/\,
then the output of the phase detector and loop filter will be positive, causing the frequency/:
(and therefore /2) to increase. Onthe other hand,if/: starts out at a higher frequencythan/),
then the output of the phase detector and loopfilter will be negative, causing the frequency/:
(and therefore f2) to decrease. After a short time, the two frequencies /| and /: will become
locked (equal) and therefore the phase difference will remain constant, and the output
frequence/; will be constant and N times the frequencyof/\.
OSCCLK Reference t1
—— Programmable -——| Phase >| Loop Filter +» VCO |
Divider Detector P
Af f =
REFDV<3:0> f, = =Nf,
eS a |
Loop
Programmable
Divider (/N)
SYN<3:0>
The C function call PLL_init( ) that we have been using at the beginning ofall of our
C programscalls the assembly language subroutine givenin Listing F.1.
Thefirst two instructions in Listing F.1 store a value of 2 in register SYNR and a 0 in
register REFDV. The value of the oscillator frequency, OSCCLK, on the DRAGON ]12-
PlusUSB is 8 MHz. Using the equation for PLLCLK in Fig. F.2, this will produce a
PLLCLK frequency of
TT
The nextinstruction in Listing F.1 will clear the register CLKSEL, which, from Fig.
a art ris it mt aed
F.2, will clear bit PLLSEL. This meansthat the bus frequency, BUSCLK,will be
It is necessary to use this bus frequency because, at this point, the PLL is not locked. The
one-instruction loop, p//J:, in Listing F.1 will wait for the PLL to lock bypolling the LOCK
bit in the CRGFLG register in Fig. F.2.
Thelast instruction in Listing F.1 will set the PLLSEL bit in the CLKSELregisterin
Fig. 5.2. This meansthat the bus frequency, BUSCLK, will now be
er ee
Thus, after calling the C function call PLZZinit( ), the bus frequency will be 24 MHz.
206 Appendix G
Appendix G
_ Constant character Usedto build a table of constant values const char seg?tbi{} = { |
| arrays Ox GF, OxOE, Ox5B, Ox4f,
x66, Ox6D, Ox 7D, 0x07,
Ox7F, Ox6F,0x77,0xIC, |
0x39, OxSE, 0x79, 0x71 |
| ;
i>
|
}
= a
| Characterstrings “This is a string” char’ qi;
char* a2;
ql = “Programming”;
q2 = “Microcontrollers”; a
Arrays A named collection of values of a particular unsigned char memb_speed[20]
| type. unsigned ones cent| * = {
, 65, 128,178,220
be
membspeed[j+2} = 0;
| x = cent([2]; ee
| Pointers An address that points to some memory char* ptr;
location, char* plus;
| char kbuf[12];
ptr = kbuf;
plus = Ny} Me Sal
}
Shift operators << (shift left) speed = val >> 2;
>> (shift right) data = data << 8;
Shorthand operators ++ (increment) tickst+;
-- (decrement) i--;
+=a+=bsameasa=at+tb a t= 5;
-=, *=,/=%= data |= ¢c;
<<=, >>=, A= &=, |= PTH &= OXFE;
mask >>= 1;
Functions return_type function_name(paraml, ...parmN){ void qstore(char c) {
local_declarations; reart+;
statements; if(rear > max)
} rear = min;
if(rear == front) {
rear=-;
j }
}
if statement if(expression){ if (readback == keycodes[i]) {
statement; key = 1;
else found = 1;
statement; }
\ else
i++;
for loop for(initial_index; terminal_index; increment) for(i = 07 1 < 16; i++) {
statement; PORTB = seg7tbl [i];
delay ();
}
while loop while(expression) while (SW1_ down()) {
statement; seg/dec (1) ;}
208 Appendix G
Index
MC9S12DG256 operation,
118-19
programming in C, 119-20
SPI registers, 123
Servo, 89, 95-98
Controlling position using PWM,
96-98
Seven-segment display (see 7-Segment
display)
SN754410 quadruple half-H driver,
89-90
Speaker, 139-40
Subroutine, (see Assembly language)
Switch
DIP, 31-36
hex keypad, 37-44, 109-10
Pushbutton, 31, 34-37
Timer, 132-46
generating pulse train, 132-34
input capture, 143-46
measuring pulse width, 144-46
output compare, 132-43
registers, 134-38
Wytec
DRAGON 12-Plus-USB, 4