Tải bản đầy đủ (.pdf) (123 trang)

PROGRAMMING AND CUSTOMIZING THE PIC MICROCONTROLLER 3rd phần 10 ppt

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.75 MB, 123 trang )

C PROGRAMMING LANGUAGE 1143
TABLE F.8 MICROCHIP C18 C COMPILER LIBRARY FUNCTIONS (CONTINUED)
FUNCTION PROTOTYPE DESCRIPTION
void Delay100TCYx(unsigned Delay in multiples of 100 instruction cycles.
char units)
void Delay1KTCYx(unsigned Delay in multiples of 1,000 instruction cycles.
char unit)
void Delay10KTCYx(unsigned Delay in multiples of 10,000 instruction cycles.
char unit)
void DisablePullups(void) Disable PORTB’s internal pull-up resistors.
unsigned char EEAckPolling(#) Generate acknowledge for Microchip EEPROM
(unsigned char control) I2C memory devices.
unsigned char EEByteWrite(#) Write a single byte to the Microchip EEPROM
(unsigned char control, I2C memory device.
unsigned char address,
unsigned char data)
unsigned int Read a single byte from the Microchip EEPROM
EECurrentAddRead(#) I2C memory device.
(unsigned char control)
unsigned char EEPageWrite(#) Write a string of data to the Microchip EEPROM
(unsigned char control, I2C memory device.
unsigned char address,
unsigned char * wrptr)
unsigned int EERandomRead(#) Read a single byte from an arbitrary address
(unsigned char control, from a Microchip EEPROM I2C memory
unsigned char address) device.
unsigned char Read a string of data
length long starting at an
EESequentialRead(#) arbitrary address in a Microchip EEPROM I2C
(unsigned char control, memory device.
unsigned char address,


unsigned char * rdptr,
unsigned char length)
void EnablePullups(void) Enable PORTB’s internal pull-up resistors.
unsigned char getsI2C(#) Read a fixed-length string from the I2C(#) bus
(unsigned char * rdptr, operating in master I2C mode.
unsigned char length)
void getsMwire(#) Read a string from the Microwire device.
(unsigned char * rdptr,
unsigned char length)
void getsSPI(#) Read a string from the SPI bus.
(unsigned char * rdptr,
unsigned char length)
(
Continued
)
Simpo PDF Merge and Split Unregistered Version -
1144 APPENDIX F
TABLE F.8 MICROCHIP C18 C COMPILER LIBRARY FUNCTIONS (CONTINUED)
FUNCTION PROTOTYPE DESCRIPTION
void getsUART(char * Read a string from the software USART.
buffer, unsigned char len)
void gets(#)USART(char * Return a string from the specified USART.
buffer, unsigned char
length)
void IdleI2C(#)(void) Return when specified I2C bus is available.
char isBOR(void) Returns nonzero if reset was caused by a
brown-out detect.
unsigned char iscntrl Return nonzero if the character is a control
(unsigned char ch) character.
unsigned char isgraph Return nonzero if the character is a graphic

(unsigned char ch) character.
unsigned char islower Return nonzero if the character is a lower-case
(unsigned char ch) alphabetic character.
char isLVD(void) Returns nonzero if reset was caused by low-
voltage detect.
char isMCLR(void) Returns nonzero if reset was caused by
MCLR pin.
char isPOR(void) Returns nonzero if reset was caused by
power-on reset.
unsigned char Return nonzero if the character is printable.
isprint(unsigned char ch)
unsigned char Return nonzero if the character is a punctuation
ispunct(unsigned char ch) character.
unsigned char Return nonzero if the character is a white
isspace(unsigned char ch) space character.
unsigned char Return nonzero if the character is an
isupper(unsigned char ch) upper-case alphabetic character.
unsigned char Return nonzero if the character is a
isxdigit(unsigned char ch) hexadecimal digit.
char isWDTTO(void) Return nonzero if reset was caused by
watchdog timer timeout.
char isWDTWU(void) Return nonzero if reset was caused by wakeup
caused by the watchdog timer.
char isWU(void) Return nonzero if reset was caused by sleep
wakeup through MCLR or interrupt.
Simpo PDF Merge and Split Unregistered Version -
C PROGRAMMING LANGUAGE 1145
TABLE F.8 MICROCHIP C18 C COMPILER LIBRARY FUNCTIONS (CONTINUED)
FUNCTION PROTOTYPE DESCRIPTION
char * itoa(int value, Convert a 16-bit signed integer to a string.

char * string)
char * ltoa(long value, Convert a signed long integer to a string.
char * string)
void NotAckI2C(#)(void) Generate I2C not acknowledge condition.
void OpenADC(unsigned char Select the pin to connect the ADC and enable
config, unsigned char config2) the ADC in the correct mode; see
documentation for config and config2 values
for different devices.
void OpenCapture#(unsigned Configure and enable input capture #.
char config)
void OpenECapture# Configure and enable enhanced input
(unsigned char config) capture #.
void OpenI2C(#) Configure the SSP(#) module.
(unsigned char sync_mode,
unsigned char slew)
void OpenMwire(#) Configure the SSP(#) module for Microwire
(unsigned char sync_mode) operations.
void OpenPORTB Configure PORTB’s interrupts and internal
(unsigned char config) pull-up resistors.
void Enable PORTB interrupt request hardware.
OpenPORTB#INT(unsigned
char config)
void OpenEPWM1(char period) Configure the enhanced PWM channel.
void OpenPWM#(char period) Configure the specified PWM channel.
void OpenSPI(#) Initialize SSP(#) module for SPI
(unsigned char sync_mode, communications.
unsigned char bus_mode)
void OpenSWSPI(void) Configure the I/O pins of the software SPI.
void OpenTimer# Configure and enable timer#.
(unsigned char config)

void OpenUART(void) Configure the I/O pins for the software USART.
void Open(#)USART Configure and enable the specified USART.
(unsigned char config,
unsigned int spbrg)
(
Continued
)
Simpo PDF Merge and Split Unregistered Version -
1146 APPENDIX F
TABLE F.8 MICROCHIP C18 C COMPILER LIBRARY FUNCTIONS (CONTINUED)
FUNCTION PROTOTYPE DESCRIPTION
void OpenXLCD Configure PIC MCU I/O pins and initialize LCD
(unsigned char lcdtype) controller.
unsigned char putsI2C(#) Write a data string to the I2C(#) bus in either
(unsigned char * wrptr) master or slave mode.
void putsSPI(#) Write a string to the SPI(#) device.
(unsigned char * wrptr)
void putsUART(char * buffer) Write a string to the software USART.
void puts(#)USART(char * Write ASCIIZ string of characters to the
data) specified USART.
void putrs(#)USART Write ASCIIZ string, defined in program memory,
(const rom char * data) of characters to the specified USART
void putsXLCD(char * buffer) Write a string to LCD controller.
int rand(void) Return a pseudorandom integer.
int ReadADC(void) Return the 16-bit result of the ADC operation.
unsigned char Return the address byte from the LCD
ReadAddrXLCD(void) controller.
void ReadCapture# Read the result of a capture event from input
capture #.
void ReadECapture# Read the result of a capture event from

enhanced input capture #.
char ReadDataXLCD(void) Read a data byte from the LCD controller.
unsigned char ReadI2C(#) Read a single byte from the I2C(#) bus; this
(void) function also may be known as getcI2C(#).
unsigned char ReadSPI(#) Read a single byte from the SPI(#) device; this
(void) function also may be known as getcSPI(#).
unsigned int ReadTimer# Read the value of Timer#.
(void)
char Read(#)USART(void) Read a byte from the specified USART; this
function also may be known as getc(#)USART.
char ReadUART(void) Read a byte from the software USART; this
function also may be known as getcUSART.
unsigned char ReadWire(#) Read a single byte from the Microwire(#)
(unsigned char high_byte, device; this function also may be known as
unsigned char low_byte) getcMwire(#).
void RestartI2C(#)(void) Generate restart condition for the I2C(#) bus.
Simpo PDF Merge and Split Unregistered Version -
C PROGRAMMING LANGUAGE 1147
TABLE F.8 MICROCHIP C18 C COMPILER LIBRARY FUNCTIONS (CONTINUED)
FUNCTION PROTOTYPE DESCRIPTION
void SetCGRamAddr Specify the LCD character generator address.
(unsigned char addr)
void SetChanADC Select the channel to be used for the ADC
(unsigned char channel) operation.
void SetCSSWSPI(void) Set the software SPI interface’s chip select pin.
void SetDDRamAddr Specify the LCD display data area address.
(unsigned char addr)
void SetOutputPWM# Set the PWM output configuration bits for ECCP.
(unsigned char outputconfig,
unsigned char outputmode)

void SetOutputEPWM1 Set the enhanced PWM output configuration
(unsigned char outputconfig, bits for ECCP.
unsigned char outputmode)
void srand(unsigned int seed) Specify the starting seed for the
rand function.
void StartI2C(#)(void) Generate a start condition for the I2C(#) bus.
void StopI2C(#)(void) Generate stop operation for I2C(#) bus.
char SWAckI2C(void) Generate an I2C bus acknowledge.
char SWGetsI2C(unsigned char Read a string from the I2C bus.
*rdptr, unsigned char length)
char SWNotAckI2C(void) Generate an I2C bus not acknowledge.
char SWPutsI2C(unsigned char Write a string to the I2C bus.
*wrptr)
char SWReadIC(void) Read a byte from the I2C bus; this function also
may be known as
SWGetcI2C.
void SWRestartI2C(void) Generate an I2C bus restart condition.
void SWStartI2C(void) Generate an I2C bus start condition.
void SWStopI2C(void) Generates an I2C bus stop condition.
char Write a byte to the I2C bus; this function also
SWWriteI2C(unsigned char may be known as SWPutcI2C.
dataout)
char tolower(char c) Convert upper-case character to lower case or
leave alone.
char toupper(char c) Convert lower-case character to upper case or
leave alone.
(
Continued
)
Simpo PDF Merge and Split Unregistered Version -

1148 APPENDIX F
TABLE F.8 MICROCHIP C18 C COMPILER LIBRARY FUNCTIONS (CONTINUED)
FUNCTION PROTOTYPE DESCRIPTION
void WriteCmdXLCD Write a command to the LCD controller.
(unsigned char cmd)
void WritedataXLCD Write a byte to the LCD data area.
(char data)
unsigned char WriteI2C(#) Write a single byte to the I2C(#) bus.
(unsigned char data_out)
unsigned char WriteMwire(#) Write a single byte to the Microwire(#) device.
(unsigned char data_out)
unsigned char WriteSPI(#) Write a single byte to the SPI(#) bus; this
(unsigned char data_out) function also may be known as putcSPI(#).
char WriteSWSPI(char data) Write a byte to the software SPI; this function
also may be known as putcSWSPI.
void WriteTimer# Write a value to timer#.
(unsigned int tmr_value)
void WriteUART(char data) Write a byte to the software USART; this
function also may be known as putcUSART.
void Write(#)USART(char data) Write a byte to the specified USART; this
function also may be known as
putc(#)USART.
Simpo PDF Merge and Split Unregistered Version -
1149
G
REUSE, RETURN, AND RECYCLE
The most efficient programmers I have ever met never write any new code. Instead, they
have built up a library of code snippets that can be reused for different applications. These
snippets are usually well known by the programmer in terms of operation, parameters,
and peculiarities. In this appendix I have included a fair number of snippets and macros

that I have found to be useful in developing my own PIC
®
microcontroller applications.
One of the biggest strengths of the PIC microcontroller is its ability to provide digi-
tal input/output (I/O) efficiently in a number of different ways. This flexibility can be
used to implement I/O functions in the PIC microcontroller that weren’t designed in orig-
inally. As part of this appendix, I present a number of simple I/O “bit banging” func-
tions that provide commonly required functions for the PIC microcontroller that can be
included into your code along with the other snippets provided.
The different features of the snippets can be modified for use in your applications with
a bit of cut and pasting in an editor. You also should be able to find useful and clever
code from other sources, including magazines, other books, and the Internet. This recy-
cling of code is why I have named this appendix after the conservationist’s “three R’s.”
While I’m not a total “free software” advocate, I do believe that small pieces of useful
code should be shared freely to help and encourage others. In this spirit, I ask that if
you find or develop a useful function out of a few instructions, please share it with other
people. If you don’t know how to do this, you can share the code with a list-server (such
as the PICList) community and let other people put it up on the Web for you. I’ll always
be happy to look at what you have come up with.
The most important piece of hardware in the PIC microcontroller used in applications
is the I/O pins. When accessing the I/O pins, remember that there are three different ways
of reading and writing them. The byte or port wide access method uses the movf and
movwf instructions to read and write to the full I/O port. Along with these instructions,
the arithmetic and bitwise instructions can be used with the port as the result’s desti-
nation to simplify and optimize the operations.
The second method is to access the I/O port pins individually using the bcf, bsf,
btfsc, and btfss instructions. These instructions work well, but when using bcf
Copyright © 2008, 2002, 1997 by The McGraw-Hill Companies, Inc. Click here for terms of use.
Simpo PDF Merge and Split Unregistered Version -
1150 APPENDIX G

and bsf, remember that they also can change the output value of a bit if the port bit is
at an unexpected or unwanted value.
The last method is to use the rotate instructions rlf and rrf to pass data in and out
of the I/O ports using the STATUS register’s carry flag. Using this method means that
either bit 0 or bit 7 of the I/O port will be accessed, but this method is very effective
and efficient. To do this, the I/O situation has to be planned so that shifting data in and
out does not affect the operation of the output values of the I/O port. This is not hard to
do—the most obvious way of doing it is to set the rest of the bits on a port as input. With
a bit of planning, shifting can be done on mixed input and output very effectively.
Useful Snippets
Here are a number of useful pieces of code that you can use in your applications. In some
cases, I am the originator of the code; in others, when I know who the originator was,
I have given him or her credit.
NEGATING THE CONTENTS OF A FILE REGISTER
Converting the contents of a file register to its twos complement value without affect-
ing the w register is simply accomplished by
comf Reg, f ; Invert the bits in the Register
incf Reg, f ; Add One to them to turn into 2’s
; Complement
This code should not be used on any special hardware control registers.
NEGATING THE CONTENTS OF THE W REGISTER
If you have to negate the contents of the w register, you could use the preceding code
(after saving the value in w into a register), or you could use for low-end devices (16C5x
or 12C5xx)
addwf Reg, w ; w = w + Reg
subwf Reg, w ; w = Reg - w
; w = Reg - ( w + Reg )
; w = -w
Any file register can be used for this code because its contents are never changed.
In mid-range parts, the single instruction

sublw 0 ; w = 0 - w
could be used.
Simpo PDF Merge and Split Unregistered Version -
REUSE, RETURN, AND RECYCLE 1151
INCREMENTING/DECREMENTING THE W REGISTER
Here are a couple of snippets that will allow you to increment and decrement the w reg-
ister without affecting any file registers if you don’t have addlw/sublw instructions
(i.e., in the case of low-end processors). Reg can be any register that does not change
during execution of the three instructions. For low-end parts, any file register can be used
because there is no danger of them being updated by an interrupt handler.
To increment:
xorlw 0x0FF ; Get 1s Complement of Number
addwf Reg, w ; w = Reg + (w^0x0FF)
subwf Reg, w ; w = Reg + ((Reg + (w^0x0FF))^0x0FF) + 1
; w = w + 1
To decrement, the instructions are rearranged:
subwf Reg, w ; w = Reg + (2^0x0FF) + 1
xorlw 0x0FF ; Get 1s Complement of Result
addwf Reg, w ; w = w - 1
ROTATING A BYTE IN PLACE
These two lines will rotate the contents of a file register in a low-end or mid-range PIC
microcontroller without losing data in the carry flag. When working with the PIC18, the
rlncf and rrncf instructions can be used for the same function. Rotates right and
left can be implemented with this snippet. Note that the carry flag is changed.
rlf Register, w ; Load Carry with the high bit
rlf Register, f ; Shift over with high bit going low
COPY BITS FROM ONE REGISTER TO ANOTHER
Here is a fast way to save specific bits from one register into another:
movf Source, w
xorwf Destination, w

andlw B’xxxxxxxx’ ; Replace “x” with “1” to Copy the Bit
xorwf Destination, f
CONVERTING A NYBBLE TO ASCII
This is a question that comes up all the time when the contents of a byte are to be
displayed/output. The most obvious way of doing this is to use a table read:
NybbletoASCII:
addwf PCL, f ; Add the Contents of the Nybble to PCL/
dt “0123456789ABCDEF” ; return the ASCII as a Table Offset
Simpo PDF Merge and Split Unregistered Version -
1152 APPENDIX G
However, I think a much better way of doing this is
NybbletoASCII: ; Convert a Nybble in “w” to ASCII
addlw 0x036 ; Add ‘0’ + 6 to Value
btfsc STATUS, DC ; If Digit Carry Set, then ‘A’ - ‘F’
addlw 7 ; Add Difference Between ‘9’ and ‘A’
addlw 0-6
return ; Return the ASCII of Digit in “w”
This method will take three instruction cycles longer than the previous code, but it
requires 12 fewer instructions.
CONVERTING AN ASCII BYTE TO A HEX NYBBLE
The code below is really a rearrangement of the preceding snippet. Using the aspect that
the high nybble of ASCII A to F is one greater than the high nybble of 0 to 9, a value is
conditionally added to make the result 0x000 to 0x00F.
ASCIItoNybble:
addlw 0x0C0 ; If “A” to “F”, Set the Carry Flag
btfss STATUS, C ; If Carry Set, then ‘A’ - ‘F’
addlw 7 ; Add Difference Between ‘9’ and ‘A’
addlw 9
return ; Return the ASCII of Digit in “w”
Note that ASCII characters other than 0 to 9 and A to F will result in an incorrect

result.
USING T0CKI AS AN INTERRUPT SOURCE PIN
Some time ago, a question came up on the PICList asking if the timer input pin could
be used as an interrupt source pin. The answer to this is yes—if the timer (and prescaler)
is set up so that the next transition will increment the timer and cause an interrupt. Here’s
some code to do it in a mid-range PIC microcontroller:
movlw B’11000000’ ; First Setup with Instruction Clock
option ; as TMR0 Source
movlw B’11100000’ ; Option Setup for TOCK1 TMR0 Source
clrf TMR0 ; Set TMR0 to 0x0FF
decf TMR0, f
option ; Enable Timer on Outside Interrupt
; Edge
; NOTE - Executing this Instruction
; after “decf” will Load the
; Synchronizer with a “1”
Simpo PDF Merge and Split Unregistered Version -
REUSE, RETURN, AND RECYCLE 1153
movlw 0x0A0 ; Enable TMR0 Overflow Interrupt
movwf INTCON ; Interrupt will occur when edge received
This code also can be used on a low-end PIC microcontroller to monitor when an input
changes instead of continuously polling the input pin.
DIVIDING BY 3
As much as you try to avoid it, sometimes you have to divide. Here’s an algorithm from
Andy Warren for dividing a positive value by 3 by knowing that divide by 3 can be rep-
resented by the series
x/3 = x/2 − x/4 + x/8 − x/16 + x/32 − x/64↑
The algorithm
int DivideBy3( int Value ) // Divide “Value” by 3 and
Return it

{
int Quotient = 0;
while (Value != 0) {
Value = Int(Value / 2); // Quotient + 1/2
if (Value != 0) {
Quotient = Quotient + Value;
Value = int(Value / 2); // Quotient – 1/4
if (Value != 0)
Quotient = Quotient - Value;
}
}
return Quotient;
} // End DivideBy3
can be implemented in mid-range PIC microcontrollers as
Div3: ; Divide Contents of “w” by 3
movwf Dividend
clrf Quotient
Div3_Loop: ; Loop Until the Dividend == 0
Simpo PDF Merge and Split Unregistered Version -
1154 APPENDIX G
bcf STATUS, C
rrf Dividend, f ; Dividend /2 (ie “x/2” in Series)
movf Dividend, w ; Is it Equal to Zero?
btfsc STATUS, Z
goto Div3_Done ; If it is, then Stop
addwf Quotient ; Add the Value to the Quotient
rrf Dividend, f ; Dividend /2 (ie “x/4” in Series)
movf Dividend, w
btfsc STATUS, Z
goto Div3_Done

subwf Quotient, f ; Quotient = Quotient-(Dividend / 4)
goto Div3_Loop
Div3_Done:
movf Quotient, w ; Return the Quotient
return
SIXTEEN-BIT COUNTER WITH A CONSTANT LOOP DELAY
When I first started working with the PIC microcontroller, I thought I was exceedingly
clever when I came up with
movlw HiDlay ; Load the Delay Values
movwf HiCount
movlw LoDlay
movwf LoCount
Dlay: ; Loop Here Until HiCount/LoCount == 0
decfsz LoCount, f
goto Dlay
decfsz HiCount, f
goto Dlay
Then Marc Heuler showed the code
movlw HiDlay ; Load the Delay Values
movwf HiCount
movlw (LoDlay ^ 0x0FF) + 1
Dlay:
addlw 1 ; Increment the Counter by 1
btfsc STATUS, Z
decfsz HiCount, f ; Decrement the High Counter
goto Dlay
Simpo PDF Merge and Split Unregistered Version -
REUSE, RETURN, AND RECYCLE 1155
This loop takes five cycles to execute regardless of whether or not HiCount is to be
decremented (and uses one less file register than my method above). The actual time

delay is calculated using the 16-bit number from
Time Dlay = 16BitDlay * 5 ins/loop * 4 clocks/ins / clock frequency
This formula is a lot easier to use than having to (correctly) figure out the delay for the
Dlay loop code given above this. In the first edition of this book, it took three print-
ings to get the first Dlay loop’s delay printed correctly.
The first method is useful if you require approximately a 200,000-instruction-cycle
delay. To implement this, rather than loading HiDlay and LoDlay with constants,
simply clear them before entering the two decfsz instruction loops.
SIXTEEN-BIT PULSE MEASUREMENT WITH FIVE-CYCLE DELAY
This is an improvement on the 16-bit delay code that I presented in the first edition
of this book. PulseWidth is a 16-bit value that contains the number of times through
the loop. The code that measures the pulse width for a high pulse is
clrf PulseWidth ; Reset the Timer
clrf PulseWidth + 1
btfss PORTn, Bit ; Wait for the Pulse to go high
goto $ - 1
incfsz PulseWidth, f ; Increment the Counter
decf PulseWidth + 1, f
btfsc PORTn, Bit ; Loop while Still High
goto $ - 3
movf PulseWidth, w ; Make 16 Bit Result Valid
addwf PulseWidth + 1, f
DETECT A CHANGE IN A REGISTER
Bob Fehrenbach has passed on a number of his snippets for inclusion on my Web page,
and they have shown up in this book. The first detects the change in a bit and saves the
new data for later execution. This code can be used to detect changes in the I/O ports,
timers, and other registers that can be updated externally to the software execution.
movf Reg, w
andlw Mask ; Mask out unused bits
xorwf old, w ; Compare to previous value

btfsc STATUS, Z ; If Zero set, bits are the Same
goto no_change
xorwf old ; Bits are different, Store New
; pattern in “old”
Simpo PDF Merge and Split Unregistered Version -
1156 APPENDIX G
TEST A BYTE WITHIN A RANGE
This is an algorithm that I continually reinvent (although I don’t think I’ve ever come
up with anything as efficient as Bob Fehrenbach’s routine):
movf Num, w
addlw 255 - hi_lim ; “Num” is equal to -hi_lim
addlw hi_lim - lo_lim + 1 ; “Num” is > 255 if it is above
btfsc STATUS, C ; the lo-lim
goto in_range
SWAP THE CONTENTS OF W WITH A REGISTER
I know that this one has been around forever, but it never hurts to be reminded of it and
to have it written down for easy access.
xorwf Reg, f ; w = w, Reg = Reg ^ w
xorwf Reg, w ; w = w ^ (Reg ^ w), Reg = Reg ^ w
; w = Reg, Reg = Reg ^ w
xorwf Reg, f ; w = Reg, Reg = Reg ^ w ^ Reg
; w = Reg, Reg = w
This algorithm for swapping values without a temporary value code can be imple-
mented in C as
VariableA = VariableA ^ VariableB; // A = A ^ B
VariableB = VariableB ^ VariableA; // B = B ^ A
// = B ^ (A ^ B)
// = A
VariableA = VariableA ^ VariableB; // A = (A ^ B) ^ A
// = B

SWAP THE CONTENTS OF TWO REGISTERS
Using the algorithm from the preceding point, here’s a fast snippet to swap the contents
of two file registers:
movf X, w
subwf Y, w ; W = Y - X
addwf X, f ; X = X + (Y - X)
subwf Y, f ; Y = Y - (Y - X)
COMPARE AND SWAP IF Y < X
Here’s a compare and swap (uses the swap presented earlier):
Simpo PDF Merge and Split Unregistered Version -
REUSE, RETURN, AND RECYCLE 1157
movf X, w
subwf Y, w ; Is Y >= X?
btfsc STATUS, C ; If Carry Set, Yes
goto $ + 2 ; Don’t Swap
addwf X, f ; Else, X = X + (Y - X)
subwf Y, f ; Y = Y - (Y - X)
CONVERT ASCII TO UPPER CASE
This is a practical application of the preceding snippet. I think that this subroutine
demonstrates how the code works quite well.
ToUpper:
addlw 255 - ‘z’ ; Get the High limit
addlw ‘z’ - ‘a’ + 1 ; Add Lower Limit to Set Carry
btfss STATUS, C ; If Carry Set, then Lower Case
addlw h’20’ ; Carry NOT Set, Restore Character
addlw ‘A’ ; Add ‘A’ to restore the Character
return
COUNTING THE NUMBER OF 1S IN A BYTE
If you’re a regular on the PICList, you will know of Dmitry Kiryashov and his interest
in providing the most efficient routines possible for carrying out operations. The code

below is his optimization of the classic problem of counting the number of 1s in a byte
in 12 instructions/12 cycles.
; (c) 1998 by Dmitry Kirashov
rrf X, w ; “X” Contains Byte
andlw 0x55 ; -a-c-e-g
subwf X, f ; ABCDEFGH
; where AB=a+b, etc.
; the same trick as in example_1
movwf X
andlw 0x33 ; CD GH
addwf X, f
rrf X, f ; 0AB00EF0
; 00CD00GH
addwf X, f ; 0AB00EF0
; 0CD00GH0
rrf X, f ; 0ABCD.0EFGH
swapf X, w
addwf X, w
andlw 0x0F ; Bit Count in “w”
Simpo PDF Merge and Split Unregistered Version -
1158 APPENDIX G
GENERATING PARITY FOR A BYTE
The six instructions below (provided by John Payson) will calculate the even parity for
a byte. At the end of the routine, bit 0 of X will have the even parity bit of the original
number. Even parity means that if all the 1s in the byte are summed along with the parity
bit, an even number will be produced.
swapf X, w
xorwf X, f
rrf X, w
xorwf X, f

btfsc X, 2
incf X, f
KEEPING A VARIABLE WITHIN A RANGE
Sometimes when handling data you will have to keep integers within a range. The four
instructions below will make sure that the variable Temp always will be in the range of
0 to Constant.
movlw Constant ; 0 <= Temp <= Constant
subwf Temp, w
btfsc STATUS, C
subwf Temp, f
SWAPPING BIT PAIRS
Another of Dmitry Kiryashov’s routines is this sequence of instructions for swapping
bit pairs in a byte in five instructions/cycles.
; (c) 1998 by Dmitry Kirashov
movwf X ; Save the Incoming Byte in
; a temporary register
; w = X = ABCDEFGH
andlw 0x055 ; w = 0B0D0F0H
addwf X, f ; X = ABCDEFGH + 0B0D0F0H
rrf X, f ; X = (ABCDEFGH + 0B0D0F0h) >> 1
addwf X, w ; w = BADCFEHG
BITWISE OPERATIONS
Setting and resetting bits based on the state of other bits is not something the PIC micro-
controller seems to be able to do well naturally. By using multiple bit condition tests,
Simpo PDF Merge and Split Unregistered Version -
REUSE, RETURN, AND RECYCLE 1159
the actual operations are pretty easy to implement. Note that these routines should not
be used for changing I/O port values because there may be an incorrect intermediate
value. If you are using this code for changing an I/O port or hardware control register
bit, make sure that you read the register’s contents into the w register and use the

andlw and iorlw instructions to change the bit before writing the new value back
to the register. The intermediate value could initiate some hardware operation that is
not desired.
Setting a bit by ANDing two others together is accomplished by
bsf Result ; Assume the result is True
btfsc BitA ; If BitA != 1 then result is False
btfss BitB ; If BitB == 0 then result is False
bcf Result ; Result is False, Reset the Bit
To show how this operation could be accomplished on an I/O port bit, I have included
the code
movf PORTB, w ; Store PORTB in “w” for “AND” Op’n
iorlw 1 << Result ; Assume the Result is True
btfsc BitA ; If BitA != 1 then result is False
btfss BitB ; If BitB == 0 then result is False
andlw 0x0FF ^ (1 << Result) ; Result is False, Reset the Bit
movwf PORTB ; Save the Result
ORing two bits together is similar to the AND operation, except that the result is
expected to be false, and when either bit is set, the result is true.
bcf Result ; Assume the result is False
btfss BitA ; If BitA != 0 then result is True
btfsc BitB ; If BitB == 0 then result is False
bsf Result ; Result is True, Set the Bit
The final operation is the NOT. There are two ways of implementing this operation
based on where the input value is relative to the output value. If they are the same (i.e.,
the operation is to complement a specific bit), the code to be used is simply
movlw 1 << BitNumber ; Complement Specific Bit for “NOT”
xorwf BitRegister, f
If the bit is in another register, then the value stored is the complement of it:
bcf Result ; Assume that the Input Bit is Set
btfss Bit ; - If it is Set, then Result Correct

bsf Result ; Input Bit Reset, Set the Result
Simpo PDF Merge and Split Unregistered Version -
1160 APPENDIX G
Mykemacs.inc
I want to introduce you some basic programming and I/O functions for low-end and mid-
range PIC microcontrollers. When I first started writing this, I was trying to figure out
how to best present them so that they could be used easily without you having to figure
out how to modify code that is cut and pasted into your application. The solution I came
up with is to provide macros for these functions.
The macros listed below are examples of quite advanced macros. Along with providing
different methods of interfacing, I also use the expected PIC microcontroller’s clock
speed to calculate delays within the macros. For the most part, they can be used with-
out modification, but you should read through the accompanying text to make sure that
you are aware of any issues with the macros on different devices.
When this code was written, it was designed for low-end and mid-range PIC micro-
controllers. You will find that for initializing interfaces, I have used the mid-range TRIS
registers instead of the common tris instructions. This was done because of the ease
with which specific pins can access specific tris bits using the bsf and bcf instruc-
tions. For low-end PIC microcontrollers, you will have to come up with your own TRIS
register values based on the I/O pins you are going to access.
All these macros, including the structured programming macros I presented earlier
in this book, are bundled up together in what I have immodestly called mykemacs.inc.
This file is located in the C:\PICDwnLd\Macros\mykemacs subdirectory of your PC’s
PIC Microcontroller directory. To use mykemacs.inc in your applications, copy the file
into your PC’s C:\Program Files\Microchip\MPASM Suite folder.
When mykemacs.inc is to be used in your application, include the file using the
statement
include “mykemacs.inc”
When you want to use a function, you can select it from the invocation instructions given
in the following sections.

SIMPLE DELAY
As I look through this book, I realize that I have not given you a good, simple delay
that will work in the generic case. The important point of the preceding sentence is the
term generic case—often a delay is needed that does not affect any other registers
or the PIC microcontroller’s STATUS bits. The DlayMacro that I have provided
below will allow you to delay any number of cycles (up to a limit of 777 cycles) and
provides the calculations to a specific instruction cycle. The macro is
DlayMacro Macro Cycles ; Delay Macro for Edges
variable i, TCycles, Value, TFlag
TCycles = Cycles
Value = 1 << 7
Simpo PDF Merge and Split Unregistered Version -
REUSE, RETURN, AND RECYCLE 1161
i = 7
TFlag = 0
if (TCycles > 5)
while (i >= 0)
if ((TFlag == 0) && ((Value * 3) <= TCycles))
bsf DlayCount, i
TFlag = 1
TCycles = TCycles - (Value * 3)
else
if ((TFlag != 0) && (((Value * 3) + 1) <= TCycles))
bsf DlayCount, i
TCycles = TCycles - ((Value * 3) + 1)
endif
endif
Value = Value >> 1
i = i - 1
endw

if (TCycles > 3)
Error “Delay Cycles too Large for Macro”
endif
decfsz DlayCount, f
goto $ - 1
endif
while (TCycles > 1)
goto $ + 1
TCycles = TCycles - 2
endw
if (TCycles == 1)
nop ; Delay the Last Cycle
endif
endm
This macro may seem quite complex, but the actual code it produces is very simple.
The macro will work on low-end, mid-range, and PIC18 PIC microcontroller
architectures.
The basis of the delay code is the three-instruction-cycle loop:
decfsz DlayCount, f
goto $ - 1
Each time this loop executes, three cycles are taken up. To set the initial DlayCount’
value, the number of cycles to delay is compared against the maximum value possible
for a bit value in DlayCount. For example, setting bit 2 of DlayCount will cause
the loop code to execute four times. If bit 6 is used, DlayCount will loop 64 times.
Instead of dividing the total number by 3 (which is the number of loops required) and
then loading the w register with the value (which changes the w register) and then writing
to the DlayCount variable, I set the appropriate bit for the count in DlayCount.
Simpo PDF Merge and Split Unregistered Version -
1162 APPENDIX G
This is a bit hard to understand (and pretty hard to see in the macro code above). To

try to make the operation more obvious, I want to show some pseudocode that better
illustrates how the delay value is calculated. The output of the code is assumed to be
the DlayCount variable with the appropriate bits set.
TotalDelay = RequestedDelay;
DlayCount = 0; // Final Value to Delay
Value = 1 << 7; // Test Value
TFlag = 0;
for (i = 0; i < 8; i++ ) { // Repeat for all 8 Bits
if ((TFlag == 0) && (TotalDelay > (Value * 3))) {
DlayCount = DlayCount | Value; // Mark the Correct Bit Value
TotalDelay = TotalDelay - (Value * 3);
TFlag = 1; // Mark that Value Changes
} else if ((TFlag != 0) && (TotalDelay > ((Value * 3) + 1))) {
DlayCount = DlayCount | Value; // Mark the Correct Bit Value
TotalDelay = TotalDelay - ((Value * 3) + 1);
}
Value = Value >> 1; // Take down the Division Value
}
The reason why one is added to the delay after the first time through is because of the
way the code is produced. The first time a DlayCount bit is set, the code becomes
bsf DlayCount, Bit
decfsz DlayCount, f
goto $ - 1
and the delay is simply calculated as 2 to the power of Bit multiplied by 3. Thus, for
bit 4 being set, the delay would be
Delay = (2 ** Bit) * 3
= (1 << Bit) * 3
= (1 << 4) * 3
= 16 * 3
= 48

When another bit is set, the code becomes
bsf DlayCount, Bit1
bsf DlayCount, Bit2
decfsz DlayCount, f
goto $ - 1
In this case, the delay is 3 times the final value in DlayCount plus 1. As a formula,
the instruction delay is
DlayMacroLoopDelay = (DlayCount * 3) + (#DlayCount Bits Set - 1)
Simpo PDF Merge and Split Unregistered Version -
REUSE, RETURN, AND RECYCLE 1163
This probably seems a bit cumbersome to work with, but the macro takes care of the
calculation for you.
After the loop has finished, the code will check for any left-over instruction cycles
and insert goto $ + 1 or nop instructions as required so that the DlayMacro exe-
cutes for exactly the specified number of instruction cycles. When using this macro, you
will have to declare DlayCount and make sure that it is accessible from within what-
ever register banks are active when the macro is invoked and that it is cleared before its
first use. On exit from the DlayMacro code, DlayCount will always be zero. To
ensure that DlayCount is always zero, never change its value.
You might be surprised to see that there is an initial check for the DlayMacro loop so
that it only executes if the number of cycles to delay is greater than five. The reason for this
is that a combination of goto $ + 1 and nop instructions can be implemented more
efficiently than the delay loop. The macro will produce the most efficient code that it can.
When you look at this code, you might be wondering just how efficient the code is.
By explicitly testing the value for a specific bit value and putting it into the delay, you
might feel like I’m not being that efficient, especially in the case of making the loop
movlw InitialValue ; Delay = InitialValue * 3 + 1
movwf DlayCount
decfsz DlayCount, f
goto $ - 1

goto $ + 1 ; Optional
nop ; Optional
which has a worst-case size of six instructions (the DlayMacro has a worst-case size
of 12 instructions). The only comment I can make to this is DlayMacro does not change
the w register, and you will find that for many cases DlayMacro will end up produc-
ing a comparable number of instructions.
For example, invoking the macro
DlayMacro 100
will produce the code
bsf DlayCount, 5
bsf DlayCount, 0
decfsz DlayCount, f
goto $ - 1
which is comparable in size with what the other code will do but does not change the
w register. This feature of not changing the w register will be useful in the following
example code. In the worst case, the DlayMacro will produce 12 instructions (to the
other method’s six). This happens in only one instance, and you will find that for most
instruction cycle delays this macro produces code that is within the four to six instruc-
tions of the other case.
Simpo PDF Merge and Split Unregistered Version -
1164 APPENDIX G
LCD INTERFACES
To simplify the work required for adding LCDs to your applications, I wanted to create
a series of four macros that would allow you to interface with LCDs in either low-end
or mid-range PIC microcontrollers using one of four subroutines. By using a consistent
interface, the same software can be used for a variety of different situations without
requiring that the application code be changed—the vision was that just a new macro
would needed to be invoked to access the LCD.
The four LCD subroutines are
LCDPORTInit—Used in mid-range devices to define the I/O ports that interface to

the LCD
LCDInit—Put the LCD into 2 line mode
LCDChar—Send an ASCII character to the LCD
LCDIns—Send an LCD instruction
Along with these subroutines, each macro creates a Dlay5 subroutine that delays
execution for 5 ms for LCD reset operations. In each LCD access type macro, I create
a 160-µs delay code that is inserted in line and based on the DlayMacro and does not
change the value of any registers.
For the most part, the interfaces are direct to the LCDs, but there are some points that
you should be aware of that I point out in the text below.
The first macro is LCD8, which provides a basic interface to the LCD with worst-
case startup delays. To invoke it, the statement
LCD8 DataPort, EPort, EPin, RSPort, RSPin, RWPort, RWPin, Frequency
is put in where DataPort is the 8-bit I/O port. EPort and EPin are the E clock def-
inition. RSPort and RSPin are the RS LCD data type input. RWPort and RWPin
are the pins used to poll the LCD for data reply (and are essentially unused).
Frequency is the PIC microcontroller operating speed and is used to calculate the
delay values. The only variable required for the LCD8 and LCD8Poll macros is the
8-bit variable Dlay.
This macro should work with any low-end or mid-range PIC microcontroller.
Note that the LCDPORTInit subroutine cannot be used with low-end PIC micro-
controllers; to set up the I/O ports, you will have to create your own tris state-
ments. The macro is
LCD8 Macro DataPort, EPort, EPin, RSPort, RSPin, RWPort, RWPin,
Freq
variable Dlay5Value, Dlay160Value, Dlay160Bit1 = -1, Dlay160Bit2 = -1
variable BitCount = 0
variable Value = 128, Bit = 7
Dlay5Value = ((5007 * (Freq / 1000) / 4000) / 7) + 256
Simpo PDF Merge and Split Unregistered Version -

REUSE, RETURN, AND RECYCLE 1165
Dlay160Value = (163 * (Freq / 1000) / 4000) / 3
while (Bit >= 0) ; Find the Number of Bits and their
; positions in “Dlay160Value”
if ((Dlay160Value & Value) != 0)
if (Dlay160Bit1 == -1) ; Set the Upper Bit
Dlay160Bit1 = Bit
else
if (Dlay160Bit2 == -1)
Dlay160Bit2 = Bit
endif
endif
BitCount = BitCount + 1
endif
Value = Value >> 1
Bit = Bit - 1
endw
if (BitCount > 2) ; Just Want max two Bits
if ((Dlay160Bit1 - 1) == Dlay160Bit2)
Dlay160Bit1 = Dlay160Bit1 + 1 ; Shift Top up by 1
Dlay160Bit2 = -1 ; Delete Second
else
Dlay160Bit2 = Dlay160Bit2 + 1 ; Shift Bottom up by 1
endif
endif
Dlay5 ; Delay 5 msecs
movlw (Dlay5Value & 0x0FF00) >> 8
movwf Dlay
movlw Dlay5Value & 0x0FF
subwf Dlay, w

xorlw 0x0FF
addwf Dlay, w
btfsc STATUS, Z
decfsz Dlay, f
goto $ - 5
return
LCDPORTInit ; Initialize the I/O Ports
bsf STATUS, RP0 ; ONLY used by mid-range
movlw 0x000
movwf DataPort
bcf EPort, EPin
bcf RSPort, RSPin
bcf RWPort, RWPin
bcf STATUS, RP0
bcf EPort, EPin
bcf RSPort, RSPin
Simpo PDF Merge and Split Unregistered Version -
1166 APPENDIX G
bcf RWPort, RWPin
return
LCDIns ; Send the Instruction to the LCD
movwf DataPort
bcf RSPort, RSPin
if (Freq > 8000000) ; Make Sure Proper Delay is In Place
if (Freq < 16000000)
nop
else
goto $ + 1
endif
endif

bsf EPort, EPin
if (Freq > 8000000) ; Make Sure Proper Delay is In Place
if (Freq < 16000000)
nop
else
goto $ + 1
endif
endif
bcf EPort, EPin
bsf Dlay, Dlay160Bit1 ; Delay 160 ␮secs
if (Dlay160Bit2 != -1)
bsf Dlay, Dlay160Bit2
endif
decfsz Dlay, f
goto $ - 1
andlw 0x0FC ; Have to Delay 5 msecs?
btfsc STATUS, Z
call Dlay5
return
LCDChar ; Send the Character to the LCD
movwf DataPort
bsf RSPort, RSPin
if (Freq > 8000000) ; Make Sure Proper Delay is In Place
if (Freq < 16000000)
nop
else
goto $ + 1
endif
endif
bsf EPort, EPin

if (Freq > 8000000) ; Make Sure Proper Delay is In Place
if (Freq < 16000000)
nop
Simpo PDF Merge and Split Unregistered Version -
REUSE, RETURN, AND RECYCLE 1167
else
goto $ + 1
endif
endif
bcf EPort, EPin
bsf Dlay, Dlay160Bit1 ; Delay 160 usecs
if (Dlay160Bit2 != -1)
bsf Dlay, Dlay160Bit2
endif
decfsz Dlay, f
goto $ - 1
return
LCDInit ; Do the 8 Bit Initialization
call Dlay5 ; Wait 15 msecs
call Dlay5
call Dlay5
movlw 0x030
call LCDIns ; Send the Reset Instruction
call Dlay5
movlw 0x030
call LCDIns
movlw 0x030
call LCDIns
movlw 0x038 ; Set Interface Length
call LCDIns

movlw 0x010 ; Turn Off Display
call LCDIns
movlw 0x001 ; Clear Display RAM
call LCDIns
movlw 0x006 ; Set Cursor Movement
call LCDIns
movlw 0x00E ; Turn on Display/Cursor
call LCDIns
return
endm
Looking at the Dlay160 code, you’ll notice that I restricted the number of possible
bit settings to two. If more than two are set, then I first increment the lower bit value.
If the lower bit value is immediately below the upper one, then I delete the lower one
and increment the upper value. This ensures that I do not delay for less than the 160 µs
required by the LCD. The reason why I held the delay down to two cycles is to keep
the delay code as simple as possible and avoid the need for invoking DlayMacro.
The LCD8Poll macro produces slightly more sophisticated code than that produced
by the LCD8 macro. Instead of providing hard-coded delays in the application, the code
polls the LCD to see if the operation is complete before continuing. This is done by
Simpo PDF Merge and Split Unregistered Version -

×