unsigned char EEByteWrite# Write a single byte to the Microchip EEPROMunsigned char control, I2C memory device.. unsigned char address, unsigned char dataunsigned int Read a single byte
Trang 1TABLE F.8 MICROCHIP C18 C COMPILER LIBRARY FUNCTIONS (CONTINUED)
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 lengthlong starting at anEESequentialRead(#) 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 )
Trang 2TABLE F.8 MICROCHIP C18 C COMPILER LIBRARY FUNCTIONS (CONTINUED)
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
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
Trang 3TABLE F.8 MICROCHIP C18 C COMPILER LIBRARY FUNCTIONS (CONTINUED)
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 enableconfig, unsigned char config2) the ADC in the correct mode; see
documentation for config and config2 valuesfor 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 )
Trang 4TABLE F.8 MICROCHIP C18 C COMPILER LIBRARY FUNCTIONS (CONTINUED)
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
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
Trang 5TABLE F.8 MICROCHIP C18 C COMPILER LIBRARY FUNCTIONS (CONTINUED)
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 randfunction.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 alsoSWWriteI2C(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 )
Trang 6TABLE F.8 MICROCHIP C18 C COMPILER LIBRARY FUNCTIONS (CONTINUED )
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
Trang 7G
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
Trang 8and 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 ing the w register is simply accomplished by
affect-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
Trang 9INCREMENTING/DECREMENTING THE W REGISTER
Here are a couple of snippets that will allow you to increment and decrement the w 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
reg-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
Trang 10However, 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
; NOTE - Executing this Instruction
; after “decf” will Load the
; Synchronizer with a “1”
Trang 11movlw 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
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
Trang 12bcf 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)
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
Then Marc Heuler showed the code
movlw HiDlay ; Load the Delay Values
Trang 13This 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
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”
Trang 14TEST 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 ^ wxorwf Reg, f ; w = Reg, Reg = Reg ^ w ^ Reg
; w = Reg, Reg = w
This algorithm for swapping values without a temporary value code can be mented in C as
imple-VariableA = imple-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):
Trang 15movf 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 Characteraddlw ‘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 Kirashovrrf 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_1movwf X
andlw 0x33 ; CD GH
addwf X, f
rrf X, f ; 0AB00EF0
; 00CD00GHaddwf X, f ; 0AB00EF0
; 0CD00GH0rrf X, f ; 0ABCD.0EFGH
swapf X, w
addwf X, w
andlw 0x0F ; Bit Count in “w”
Trang 16GENERATING 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
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
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 Kirashovmovwf X ; Save the Incoming Byte in
; a temporary register
; w = X = ABCDEFGHandlw 0x055 ; w = 0B0D0F0H
Trang 17micro-the actual operations are pretty easy to implement Note that micro-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
Trang 18I want to introduce you some basic programming and I/O functions for low-end and 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
mid-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 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
micro-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
Trang 19The 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.
Trang 20This 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);
Trang 21This 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
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
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
Trang 22LCD 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
worst-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 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.
def-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 = -1variable BitCount = 0
variable Value = 128, Bit = 7
Dlay5Value = ((5007 * (Freq / 1000) / 4000) / 7) + 256
Trang 23Dlay160Value = (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 = 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
LCDPORTInit ; Initialize the I/O Ports
bsf STATUS, RP0 ; ONLY used by mid-range
Trang 25by 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
Trang 26putting the DataPort into input mode and then strobing the E bit (with RS reset and
RW set) and looking at bit 7 of the I/O port The macro code is
LCD8Poll Macro DataPort, EPort, EPin, RSPort, RSPin, RWPort, RWPin,Freq
variable Dlay5Value, Dlay160Value, Dlay160Bit1 = -1, Dlay160Bit2
if ((Dlay160Value & Value) != 0)
if (Dlay160Bit1 == -1) ; Set the Upper Bit
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
Trang 27LCDPORTInit ; Initialize the I/O Ports
bsf STATUS, RP0 ; ONLY used by mid-range
Trang 28LCDInit ; Do the 8 Bit Initialization
call Dlay5 ; Wait 15 msecs
call Dlay5
call Dlay5
movlw 0x030
movwf DataPort
Trang 29if (Freq > 8000000) ; Make Sure Proper Delay is In Place
bcf EPort, EPin ; Send the Reset Instruction
bsf Dlay, Dlay160Bit1 ; Delay 160 usecs
Trang 30title “LCD8Poll - Test out the 8 Bit LCD Interface”
;
; This Code Sends the data to the LCD in 8 Bit mode with
; Polling and sends an ASCII “A” to the LCD
;
;
; Hardware Notes:
; The PIC is a 16C84 Running at 4 MHz
; Reset is tied directly to Vcc and PWRT is Enabled
; PortB is the Data Port
; RA0 is the “E” Bit
CBLOCK 0x00C ; Start Registers at End of the Values
Dlay ; 8 Bit Delay Variable
ENDC
PAGE
CONFIG _CP_OFF & _XT_OSC & _PWRTE_ON & _WDT_OFF
; Note that the WatchDog Timer is OFF
; Demo Code, Loop Forever Toggling PA0 (Flashing the LED)
org 0
call LCDPORTInit
Trang 31call LCDInit ; Initialize the LCD Port
movlw “A” ; Put the “A” on the LCD
if ((DataBit != 0) && (DataBit != 4))
error “Invalid ‘DataBit’ Specification - Can only be ‘0’ or ‘4’”endif
while (Bit >= 0) ; Find the Number of Bits and their
; Positions in “Dlay160Value”
if ((Dlay160Value & Value) != 0)
if (Dlay160Bit1 == -1) ; Set the Upper Bit
Trang 32Dlay160Bit1 = 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
LCDPORTInit ; Initialize the I/O Ports
bsf STATUS, RP0 ; ONLY used by mid-range
Trang 34LCDChar ; Send the Character to the LCD
movwf LCDTemp ; Save the Value
Trang 36bsf EPort, EPin ; Send Another Reset Instruction
if (Freq > 8000000) ; Make Sure Proper Delay is In Place
bsf EPort, EPin ; Send the Third Reset Instruction
if (Freq > 8000000) ; Make Sure Proper Delay is In Place
Trang 37The last LCD interface macro that I am providing you with is the two-wire LCD face This interface is quite a bit slower than the others that I have presented, but it uses the fewest PIC microcontroller I/O pins The circuit schematic for this interface was shown earlier in the book (as are the schematics for all the LCD interfaces) The LCD2 macro requires only the Dlay and LCDTemp variables
inter-LCD2 Macro ClockPort, ClockPin, DataPort, DataPin, Freq
variable Dlay5Value, Dlay160Value, Dlay160Bit1 = -1, Dlay160Bit2
if ((Dlay160Value & Value) != 0)
if (Dlay160Bit1 == -1) ; Set the Upper Bit
Dlay160Bit1 = Bit
Trang 38Dlay160Bit1 = 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
LCDPORTInit ; Initialize the I/O Ports
bsf STATUS, RP0 ; ONLY used by mid-range
Trang 39movf LCDTemp, w ; Shift out the Upper 4 Bitsswapf LCDTemp, f
bsf LCDTemp, 5 ; Make LCDTemp Correct for Shiftingbcf LCDTemp, 4 ; This is “RS” Bit
bcf DataPort, DataPin ; Shift Out Each Bit
btfsc LCDTemp, 5 ; 5 is the Current MSB
bsf DataPort, DataPin ; Shift Out the Next Highest Bitbsf ClockPort, ClockPin
bcf ClockPort, ClockPin
rlf LCDTemp, f
decfsz Dlay, f
goto $ - 7
bsf DataPort, DataPin ; Latch in the Data
if (Freq > 8000000) ; Make Sure Proper Delay is In Place
movwf LCDTemp ; Shift out the Low Nybble
bsf Dlay, 2 ; Dlay = 6 for Shift Out
bsf Dlay, 1
bsf LCDTemp, 5 ; Make LCDTemp Correct for Shiftingbcf LCDTemp, 4 ; This is “RS” Bit
bcf DataPort, DataPin ; Shift Out Each Bit
btfsc LCDTemp, 5 ; 5 is the Current MSB
bsf DataPort, DataPin ; Shift Out the Next Highest Bitbsf ClockPort, ClockPin
bcf ClockPort, ClockPin
rlf LCDTemp, f
decfsz Dlay, f
goto $ - 7
bsf DataPort, DataPin ; Latch in the Data
if (Freq > 8000000) ; Make Sure Proper Delay is In Place
Trang 40bsf Dlay, Dlay160Bit1 ; Delay 160 usecs
movwf Dlay ; w still equals 6
movf LCDTemp, w ; Shift out the Upper 4 Bitsswapf LCDTemp, f
bsf LCDTemp, 5 ; Make LCDTemp Correct for Shiftingbsf LCDTemp, 4 ; This is “RS” Bit
bcf DataPort, DataPin ; Shift Out Each Bit
btfsc LCDTemp, 5 ; 5 is the Current MSB
bsf DataPort, DataPin ; Shift Out the Next Highest Bitbsf ClockPort, ClockPin
bcf ClockPort, ClockPin
rlf LCDTemp, f
decfsz Dlay, f
goto $ - 7
bsf DataPort, DataPin ; Latch in the Data
if (Freq > 8000000) ; Make Sure Proper Delay is In Place