| Home | Products | Prices | Directory | Order | Contact | New | Books | Files | Links | Other |
| Bottom of Page |
DonTronics Pages Duplicated:
DonTronics goes to great lengths to provide new and interesting material
for visitors. It has been found in recent months that other Webmasters
are duplicating material that has already been presented here. This may
be with, or without the permission of the original Author. If you see a
copy of the following files or pages, you will know DonTronics was responsible
for the initial release, and the others are simply copies. It would
be nice if these people did their own ground work, and put in the time
to get new material, and not benefit from the time and effort that DonTronics
has already invested to make this a better site.
AVR AT90S8515 getting
started code. by David VanHorn Original Copy 4-Sep-98
PIC Source Book/Disk
by Scott Edwards Electronics. Free public release. Original Copy 3-Apr-98
| What's Really HOT at DonTronics?
|
by Scott Edwards, 1994
As a result of these changes, the Source Book is reaching the end of its commercial life. Rather than discontinue it, the author has decided to make it available free via the Internet.
Free means free. You may use this material in any way you wish without obligation to the author. The author bears no liability for the consequences of such use, and will not support users of this free edition in any way. He will not be obliged to answer questions or respond to comments. Any of you who have been besieged with e-mail during Senior Project season will appreciate the wisdom of getting this out in the open right up front!
The book lists some utilities and extras on "the accompanying disk." Unfortunately, people other than the author hold the rights to these items, so they cannot be included with this online freebie.
PIC is a registered trademark of Microchip Technologies Inc.; BASIC Stamp is a registered trademark of Parallax Inc.
For those that keep asking, the executable files mentioned in the sentence above means any .exe file. All executable files have been removed. This is to overcome copyright problems with the distribution of the disk. If you need the executable files, you will have to get them yourself. They should still be available on the Parallax site as given above.
Download the PIC
Source Book/Disk. 129K.
After downloading you can then use the command:
pkunzip -d seepsb.zip a:
to write the correct directory structure to a floppy in your A: Drive.
The routines are based on the instruction set of the BASIC Stamp ®, a PIC-based single-board microcontroller. A PIC on the Stamp circuit board is programmed with a BASIC interpreter. The PIC reads instructions and data stored in an EEPROM and interprets them to execute control programs.
The Stamp's version of BASIC, known as PBASIC, includes high-level instructions for common control tasks, such as serially sending and receiving data, debouncing pushbuttons, measuring and generating pulses, reading a potentiometer, and producing sound. The routines in this book are clones of those PBASIC commands that (in most cases) function the same way.
There are three audiences for this book:
You should be comfortable with the terminology of digital logic and microcontrollers. I'm not going to be shy about using standard abbreviations (such as EEPROM, mA, µs) or computer-science terms (such as bit, byte, loop, hex, binary, AND, OR, XOR). You should also know a little BASIC, since most program structure is explained in terms of PBASIC. Finally, you should have the Parallax assembler software and manuals. You may use programming hardware from other vendors, but the Parallax assembler is required to use these routines.
This book is not a disassembly of the PBASIC interpreter. That's proprietary to Parallax, who would be crazy to give it away. Besides, it includes overhead code to make the language work properly, and upon which each of the commands depends. This would limit the usefulness of a disassembly as a toolkit of standalone routines.
Many PIC applications use a particular I/O pin for only one or two purposes, and can do without the overhead required to implement pin and port independence (PPI). Although most of the program listings in the book use PPI techniques, the accompanying disk contains non-PPI versions of some of the longer I/O routines.
When the PIC executes a call, it clears bit 8 of the program counter. This means that if you attempt to call code stored at address 256, you'll end up at address 0 instead. The Parallax assembler won't allow you to make this error; it will halt assembly and supply the line number of the offending call.
If you use more than a few of these routines in your program, you will very quickly fill the lower 256 words of a program-memory page. What can you do if you run out of lower-half-page code space?
Start by examining the routines you're using and seeing which can be converted to straight-line code. That means inserting them into the body of your program where the call would normally be. Remove any returns (ret) and replace them with jumps (jmp) to appropriate points in your code.
Also, examine the routines and try to strip out code that's not required for your purposes. For example, some of these routines start out by converting timing values to their two's complement. You could save overhead by precomputing these values or consolidating the code that does the conversion.
One last technique is "call forwarding." Put the subroutine label and a jump (jmp) instruction in the lower half of the page. Locate the real guts of the routine, complete with the return (ret) in the upper half of the page. Calling the routine sends program execution to the jmp instruction, which redirects it to the actual subroutine, located elsewhere. This can save your bacon when you need one more subroutine.
Branch is the PBASIC equivalent of the ON x GOTO statement of older BASICs, and the CASE statement of newer ones. The advantages are similar to those of CASE statements over multiple IF/THENs: The code is more compact, more understandable, and executes more quickly. It also forces the programmer to consider every possible combination of inputs and explicitly program the responses. With comparisons, it's easy to disregard "impossible" cases and let them slip through the cracks.
In assembly, the Branch instruction is coded as a jump table. You put the number of the routine to call into w, then execute a jmp pc+w instruction. This is followed by a series of normal jmps to the desired destinations, starting with the one that should execute if w = 0.
There are a couple of restrictions on using this technique. First, the jump table must be located in the first 256 words of a 512-word program-memory page because of the way the PIC's computed-jump mechanism works. Second, the program must ensure that the number in w never exceeds the number of the highest entry in the table. For example, say a jump table has 6 entries. Jumping into the table with a 0 in w would invoke the first jump, 1 the second...and so on, up to 5. If w contained 6, jmp pc+w would jump completely over the table!
One powerful use for the jump-table technique is handling a series of compound logic statements of the form IF A AND (B OR C) BUT NOT D, THEN ACTION... Take each of the elements, A through D in this case, and assign it a bit flag in the lower bits of a variable. For example, for a variable called flags, the program would include equate directives of the form A = flags.0, B = flags.1, and so on.
Next, translate your logic statements into a series of true/false (1/0) entries for each of the elements. The example above works out to 0111, 0101 and 0011, where A is the least-significant bit.
Finally, set up a jump table with entries to accommodate every combination of A, B, C, and D, represented by the numbers 0000 through 1111 binary. If you later have to modify the logic of the program, it's a simple matter of moving the jump-table entries around. To use the jump table, move flags into w and go to the instruction jmp pc+w. The program executes the routine corresponding to the number in w.

; BRANCH offset (in w), jump table ; Jumps to the routine corresponding to offset in the jump table. ; Makes evaluating compound logic expressions easy. ; Since this example uses data straight from port RA, it could move RA ; into w, then go to the jump table. However, we're using a separate variable ; to demonstrate the technique. org 8 flags ds 1 ; Variable to hold bits. temp ds 1 ; Variables for delay temp2 ds 1 ; in Action. A = flags.0 ; The elements of our B = flags.1 ; compound logic expression. C_ = flags.2 ; "C" is carry bit D = flags.3 ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 start mov !ra, #255 ; All inputs for switches. mov !rb, #0 ; All outputs for LEDs clr rb ; Ensure that LEDs are off. clr flags ; Initialize flags. ; For the sake of the example, we'll move each bit into place separately. ; However, always look for opportunities to move several bits at a time. ; For example, the four movb operations below could be replaced with ; mov flags,ra. Make sure that you strip off any extraneous bits if you use ; a port that is wider than the desired number of bits. Get_bits movb A,ra.0 ; Move bits of ra into flags. movb B,ra.1 movb C_,ra.2 movb D,ra.3 ; We want to jump to Action IF A AND (B OR C) BUT NOT D. ; That corresponds to the bit patterns 0111, 0101 and 0011, ; (see diagram below) table entries 7, 5 and 3. ; Bits: | D | C_ | B | A | ; Flags: | flags.3 | flags.2 | flags.1 | flags.0 | ; BRANCH (cont) Branch mov w,flags ; Move flags into w. jmp pc+w ; Table of 16 possible actions. jmp Get_bits ; DCBA=0000: try again. jmp Get_bits ; DCBA=0001: try again. jmp Get_bits ; DCBA=0010: try again. jmp Action ; DCBA=0011: Action! jmp Get_bits ; DCBA=0100: try again. jmp Action ; DCBA=0101: Action! jmp Get_bits ; DCBA=0110: try again. jmp Action ; DCBA=0111: Action! jmp Get_bits ; DCBA=1000: try again. jmp Get_bits ; DCBA=1001: try again. jmp Get_bits ; DCBA=1010: try again. jmp Get_bits ; DCBA=1011: try again. jmp Get_bits ; DCBA=1100: try again. jmp Get_bits ; DCBA=1101: try again. jmp Get_bits ; DCBA=1110: try again. jmp Get_bits ; DCBA=1111: try again. ; When the switches on port RA are in the correct combination, ; the LEDs on port RB blink. Action XOR rb,#255 ; Toggle LEDs. :loop djnz temp,:loop ; Short delay. djnz temp2,:loop jmp Get_bits ; Resume checking RA.
Mechanical contacts bounce, producing several milliseconds of on/off noise before they settle into a new position. This is a problem for controllers, because it can be hard to distinguish between a valid switch press and contact bounce.
Many applications can get by without debouncing a switch input, if they read the switch infrequently, or if the switch triggers a fairly long event. But switches that are used to scroll through a range of values or toggle an action definitely require debouncing. To use Button, your program must set the bit state equal to the state of the button when activated, set or clear rpt_sw to turn autorepeat on or off, put the desired pin and port into variables of the same names, and call btn_init. Then include call Button in the main program loop. Button will debounce the switch and jump to the routine named theAction when the switch is activated.
Unlike PBASIC's Button, this version does not permit changes to the delay and repeat values while the program is running. If you wish, you may use variables instead of constants to change these on the fly.
This Button routine also does not directly support multiple buttons, but can be modified to do so. The bit flags for a second button could be stored in the upper four bits of flags. A separate 16-bit db variable, accessed by a pointer, would also be necessary.

; BUTTON pin, port, state, delay, repeat_rate ; This routine debounces input from a pushbutton and jumps to an ; "action" routine. The pin (0-7), port (0-2 for RA through RC), ; action state of the input (0,1) are all specified at run time. ; The delay and repeat rate are constants. org 8 dbH ds 1 ; MSB of 16-bit debounce counter. dbL ds 1 ; LSB of 16-bit debounce counter. pin ds 1 ; Pin no. (0-7). port ds 1 ; Port no. (0-2). flags ds 1 ; Bit flags for use by Button: state = flags.3 ; Do action when button is in this state. rpt_sw = flags.2 ; Repeat switch: 0=off; 1=on. old = flags.1 ; Previous state of button. new = flags.0 ; Current state of button. ; You can tune the constants assigned to delay, repeat, and upval below by ; substituting different numbers for 150, 10, and 255. The "^0FFh" inverts ; the values to simplify the program code by allowing loops to count up ; instead of down. If ^0FFh is removed, delays will be smaller when the ; numbers are larger. delay = 150 ^0FFh ; Delay before autrepeat. repeat = 10 ^0FFh ; Delay between repeats. upval = 255 ^0FFh ; Debounce for button-up. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 ; Table to convert numbers 0-7 into bytes with a 1 in the corresponding ; bit position. For example, 3 converts to 00001000b. Pinz jmp pc+w retw 1,2,4,8,16,32,64,128 start mov !rb, #0 ; Make RB output to drive LEDs. mov !ra, #15 ; Make RA input for button. clr rb ; Turn off LEDs. clr flags ; Clear the bit flags. clr dbL ; Clear the debounce counter. clr dbH setb state ; Set up for active-high (1=pushed) setb rpt_sw ; Turn on autorepeat. setb old ; Initialize old state of button. mov pin,#2 ; Read button on pin 2 mov port,#0 ; of port RA. call btn_init ; Initialize button variables. ; This illustrates how button is used--the routine is called from a main ; loop that may or may not contain other instructions. The more instructions ; inside the loop, the smaller the button delay and repeat values should be. :loop call button goto :loop ; Since Button is called frequently from within a main program loop, it makes ; sense to move code that only needs to be executed once into a separate ; initialization routine. btn_init mov w,pin ; Look up pin value in table and call Pinz ; store it for use by Button. mov pin,w ADD port,#ra ; Add offset to point to I/O ports. ret Button mov fsr,port ; Point to the port. mov w,indirect ; Move port bits into w. sb state ; IF state=0 THEN w = NOT(port bits). mov w,/indirect AND w,pin ; IF w AND pin THEN new = 1 movb new,z ; ELSE new = 0 (0 means button "on") mov w,#3 ; Move 0000011b to w to mask bits AND w,flags ; other than old and new. jmp pc+w ; Jump based on state of bits old, new. jmp :held ; old = 0, new = 0 - button held down. jmp :release ; old = 0, new = 1 - button just released. jmp :push ; old = 1, new = 0 - button just pushed. jmp :up ; old = 1, new = 1 - button left up. :push clrb old ; Copy new button state to old. test dbL ; If dbL is not 0, then the last button-up sz ; period was short--probably a bounce-- ret ; so don't do theAction. Else, load delay mov dbH,#delay ; and jump to theAction. jmp theAction :held incsz dbL ; Increment 16-bit db variable until it ret ; reaches 0. When it does, reload it with incsz dbH ; the repeat delay , and, if the repeat ret ; switch is on, do theAction. If repeat mov dbH,#repeat ; switch is off, skip theAction and return. snb rpt_sw jmp theAction ret :release mov dbL,#upval ; Button released: put upval into setb old ; dbL and copy new state to old. This ret ; sets up the button-up debounce in :up. :up test dbL ; Increment dbL until it equals 0. sz ; Since :push won't do theAction unless inc dBL ; dbL=0, this debounces button release. ret ; This is the code activated by Button. Note that it ends with ret in order to get ; back to the code that called Button in the first place. theAction XOR rb,#255 ret
Debug can be a great help in figuring out what's happening inside a running PBASIC program. You can display the value of any bit, byte, or word of memory in a window on the PC screen. Of course, Debug depends on the Stamp programming cable and firmware routines to communicate with the PC. Not every PIC application will have these luxuries. Here are some suggestions on debugging assembly-language programs with a minimum of overhead:
One of the best features of the BASIC Stamp hardware is the 93LC56 serial EEPROM. The Stamp uses this device for program space, lookup tables, and nonvolatile data storage.
The program listing presents a series of subroutines that read, write, erase, write-protect, and write-enable the 93LC56. These routines are not port- and pin-independent. The program is based on the circuit shown in the illustration. If you need to change this circuit, make sure to change both the equates for D, CLK and CS, and the data-direction settings for the EEPROM port (mov !ra,x). These are at the start of the program, and in the subroutines EEread and Busy.
The program, which originally appeared in Parallax PIC Application Note #11, writes a rotating bit pattern to the EEPROM, then plays it back on a bank of eight LEDs connected to port RB. Not very exciting, but it verifies every storage cell of the EEPROM with a minimum of programming. Not all programs require the entire set of subroutines. Some are included just for the sake of demonstrating all the EEPROM's capabilities. For example, most programs can do without the routine that erases individual bytes, since a write accomplishes the same thing. And very few programs need to completely erase the EEPROM, or write a single value to all cells. So you should be able to remove EErase, EEwipe, and EEwrall to conserve program-memory space.
For more information on the EEPROM's characteristics and capabilities, please see the current edition of the Microchip Embedded Control Handbook.

; EEPROM (related subroutines) ; This is a collection of subroutines for reading, writing, and ; erasing the 93LC56 (or 'C56) serial EEPROM. As a demonstration, it ; writes a scanning pattern to the 256 bytes of the EEPROM, and then ; reads it back to eight LEDs connected to port rb. D = ra.0 ; Pins DI and DO of the EEPROM CLK = ra.1 ; Clock pin--data valid on rising edge CS = ra.2 ; Chip select--high = active ROP = 192 ; Opcode for read WROP = 160 ; Opcode for write EWEN = 152 ; Opcode to enable erase and write EWDS = 128 ; Opcode to disable erase and write ERAS = 224 ; Opcode to erase a byte ERAL = 144 ; Opcode to erase all bytes WRAL = 136 ; Opcode to write all bytes org 8 temp ds 1 ; Temporary variable for EE routines EEdata ds 1 ; Data to EEwrite/wrall, from EEread EEaddr ds 1 ; Address to EErase, EEwrite, EEread clocks ds 1 ; Number of clock cycles for SHiftOUT tick1 ds 1 ; Timer for demo. tick2 ds 1 ; Timer for demo. device pic16c54,xt_osc,wdt_off,protect_off reset start org 0 start mov ra,#0 ; Clear ports mov rb,#0 mov !ra,#0 ; Make all ra pins outputs initially. mov !rb,#0 ; Output for LEDs. call EEnable ; Turn off write/erase protection. mov EEdata,#1 ; EEdata = 1. mov EEaddr,#0 ; Start at address 0. :loop call EEwrite ; Write pattern to EEPROM call Busy ; Wait until done writing. rr EEdata ; Rotate the pattern right. ijnz EEaddr,:loop ; FOR EEaddr = 0 to 255... call EEdisbl ; Turn write/erase protection back on. :loop2 call EEread ; Now read back contents of EEPROM. mov rb,EEdata ; Display the EEPROM data on LEDs. inc EEaddr ; Set up for next address. call delay ; Delay to slow down the scanning. goto :loop2 ; Do forever. ; Shout shifts out the bits of the variable temp to the EEPROM data line. ; All of the other EEPROM routines call Shout. Shout rl temp ; Rotate bit7 of temp into carry movb D,c ; Move carry bit to input of EEPROM setb CLK ; Clock the bit into EEPROM nop ; Clock must be high > 500 ns clrb CLK djnz clocks,Shout ; FOR I = 1 TO clocks... ret ; Read the byte in EEaddr into EEdata. EEread mov temp,#ROP ; Move the read opcode into temp mov clocks,#4 ; Number of bits to shift out (op+1) setb CS ; Select the EEPROM. call Shout ; Shift the read opcode out. mov clocks,#8 ; Number of bits to output (8 addr bits). mov temp,EEaddr ; Set up to send the address. call Shout ; Shift out the address. mov !ra,#1 ; Now, change D to input. mov clocks,#8 ; Number of bits to input (8 data bits). :read setb CLK ; Data valid on rising edge of clock. movb c,D ; Data bit into carry. rl temp ; Rotate carry into temp. clrb CLK ; Falling edge of clock. djnz clocks,:read ; FOR I = 1 TO 8... mov EEdata,temp ; Move temp into EEdata mov !ra,#0 ; Restore D to output. clrb CS ; Deselect the EEPROM. ret ; Call to unprotect after EEdisbl or power up or any time EEPROM ; has been write protected. EEnable setb CS ; Select the EEPROM. mov clocks,#12 ; Set up to shift out opcode. mov temp,#EWEN ; temp = erase/write-enable op code. call Shout ; Shift temp out to the EEPROM. clrb CS ; Done. Deselect EEPROM. ret ; Call to protect against accidental write/erasure. Manufacturer advises ; leaving EEPROM in write-protected state most of the time to prevent accidental ; alteration by a runaway program. EEdisbl setb CS ; Select the EEPROM. mov clocks,#12 ; Set up to shift out opcode. mov temp,#EWDS ; temp = erase/write-disable op code. call Shout ; Shift temp out to the EEPROM. clrb CS ; Done. Deselect EEPROM. ret ; Write the byte in EEdata to EEaddr. EEwrite mov temp,#WROP ; Get the write op code. mov clocks,#4 ; Prepare to shift out 4 msbs. setb CS ; Select the EEPROM. call Shout ; Shift temp out to the EEPROM. mov clocks,#8 ; Now prepare for 8 addr bits. mov temp,EEaddr ; Move address into temp. call Shout ; Shift temp out to the EEPROM. mov clocks,#8 ; Prepare for 8 data bits. mov temp,EEdata ; Move data into temp. call Shout ; Shift temp out to the EEPROM. clrb CS ; Done. Deselect EEPROM. ret ; Erase the byte in EEaddr. Erasure leaves 0FFh (all 1s) in the byte. EErase mov temp,#ERAS ; Get the erase op code. mov clocks,#4 ; Prepare to shift out 4 msbs. setb CS ; Select the EEPROM. call Shout ; Shift temp out to the EEPROM. mov clocks,#8 ; Prepare for 8 addr bits. mov temp,EEaddr ; Move address into temp. call Shout ; Shift address out to the EEPROM. clrb CS ; Done. Deselect EEPROM. ret ; Erase the entire EEPROM--all 256 bytes. Call before using EEwrall below. EEwipe setb CS ; Select the EEPROM. mov temp,#ERAL ; Get the erase-all opcode. mov clocks,#12 ; Prepare to shift out 12 bits. call Shout ; Send op code + don't-care bits. clrb CS ; Done. Deselect EEPROM. ret ; Write the byte in EEdata to every address. Call EEwipe first. EEwrall setb CS ; Select the EEPROM. mov temp,#WRAL ; Get the write-all opcode. mov clocks,#12 ; Prepare to shift out 12 bits. call Shout ; Send op code + don't-care bits. mov clocks,#8 ; Prepare to shift out 8 data bits. mov temp,EEdata ; Move data into temp. call Shout ; Shift temp out to EEPROM. clrb CS ; Done. Deselect EEPROM. ret ; Check flag to determine whether the EEPROM has finished its self-timed ; erase/write. Caution: This will lock up your program until D goes high. Busy mov !ra,#1 ; Set D to input. setb CS ; Select the EEPROM. :wait jnb D,:wait ; Wait until D goes high (end of write). clrb CS ; Done. Deselect EEPROM. mov !ra,#0 ; Restore D to output. ret ; Delay routine for demo. Delay djnz tick1,Delay djnz tick2,Delay ret
The assembly language sleep instruction does for a PIC what the End instruction does for the BASIC Stamp; it shuts down the clock oscillator, halting program execution and reducing current draw to a few microamperes. If the PIC's watchdog timer is disabled (by a wdt_off switch in the device directive), that's it until the PIC is reset.
However, if the watchdog timer is on, the PIC will reawaken at the next timeout. If you want a watchdog-protected PIC to stay asleep, you must include code that checks the timeout and power-down bits (to and pd) for 00 and immediately puts the PIC back to sleep. See the section on the Sleep instruction for more information.
The For/Next loop is extremely versatile. It replaces many different kinds of loop structures used in assembly language. This section will not present a For/Next instruction per se, but will show you how to build appropriate loops for your assembly-language programs.
The most basic use of a For/Next loop is to execute some instructions a fixed number of times:
FOR I = 1 TO 3 GOSUB FLASH_LED ' Flash LED 3 times. NEXTThe instructions within the loop can also use the control variable (I in the examples here):
FOR I = 50 TO 100 pins = I ' Output 50,51,52...100 NEXTAlthough the control variable is frequently incremented by 1 each time through the loop, the programmer can have the loop count backwards or by some value other than 1:
FOR I = 1000 TO 100 STEP -2 ' Output low byte of pins = I ' 1000,998...100 NEXTPBASIC's For/Next instructions support all of these loops, using bits, bytes, or words for the control variable, start value, end value, and step. The interpreter seems to use the same mechanism, based on 16-bit comparisons, regardless of the values and variables used.
In assembly language, programmers use different loop structures depending on the number of iterations required and whether or not the value of the control variable will be used within the loop. These specialized loops take no more time (instruction cycles) or space (program memory) than is absolutely required to do the job.
Take the first example. To perform some instructions from 1 to 256 times calls for a djnz loop based on the Parallax decrement-and-jump-if-not-zero instruction. Here's an example. It assumes that the variable I has been defined, and that there's a subroutine called flash_LED somewhere in the program.
mov I,#3 ; I = 3. :loop call flash_LED ; Flash the LED. djnz I,:loop ; I=I-1: IF I=0 THEN GOTO :loopThe code inside this loop (call flash_LED) executes at least once regardless of the initial value of I. It may seem strange at first, but to get the maximum number of iterations, I must initially be assigned a value of 0. The djnz instruction decrements the variable before it makes the looping decision. In eight-bit math, 0 decrements to 255.
Let's try a 16-bit example of the same sort of loop. We'll flash the LED 500 times. We can't use djnz, because it only works with eight-bit values. In fact, for the most compact loop, we don't want to count backwards at all. When you do 16-bit operations on an 8-bit machine, it is faster to add the binary equivalent of a negative number (called the two's complement) than it is to subtract a positive one. Many of the routines in this book do just that; see Let x = x + y, or look at the code at the beginning of Pulsout. Those routines convert input values to their two's complements.
Assuming that you know the number of iterations ahead of time, you can do the two's complement conversion. The easiest way is to use a calculator capable of binary math (a must-have for assembly programming, in my opinion), set it for 16-bit operations, then subtract the number from 0. In hex, 500 is 1F4h, and 0 - 1F4h = FE0Ch. An alternative method for taking the two's complement is to take the NOT of the number and add 1.
For the loop, we will count upward from FE0Ch to 0.
mov hi,#0FEh ; hi=0FEh mov lo,#0Ch ; lo=0Ch (FE0Ch = -500 in 2's complement) :loop call flash_LED ; Flash the LED. ijnz lo, :loop ; lo=lo-1: IF lo=0 THEN GOTO :loop ijnz hi, :loop ; hi=hi-1: IF hi=0 THEN GOTO :loopNote that this loop executes the second ijnz instruction only when lo rolls over to 0. This means that those trips through the loop take 3 instruction cycles longer than trips in which lo does not roll over. This is OK in most cases, but timing loops should always take the same number of cycles. For an example of an alternative 16-bit timing loop, see the :loop portion of the Pulsout listing.
Now look at the second type of For/Next loop--the one in which the control variable is used by the code inside the loop. In such cases the control variable may not start or end on easy-to-detect values like 0. Assuming that all values fit into byte-wide variables, we end up with a loop that looks like this:
mov I,#50 ; I = 50. :loop mov rb,I ; rb = I. inc I ; I=I+1. cjbe I,#100,:loop ; IF I <= 100 THEN GOTO :loopIf you needed to increment I by some other value, say 5, you could substitute ADD I,#5 for inc I in the example. This would be equivalent to FOR I = 50 TO 100 STEP 5 in BASIC. You would have to be careful here to avoid a potentially frustrating bug: What if the STEP value is larger than the difference between the stop value and 255? In other words, you wanted to use this structure to write FOR I = 100 to 250 STEP 60. Look at the values of I through several iterations of the loop: 100, 160, 220, 24... Since I is an eight-bit variable, it rolls over when you add 220 + 60. Since 24 is less than 200, the loop keeps executing. You can prevent this runaway situation by sampling the carry bit and exiting the loop when a carry occurs:
mov I,#100 ; I = 100. :loop mov rb,I ; rb = I. ADD I,#60 ; I=I+60. jc :exit ; IF I > 255 THEN GOTO :exit cjbe I,#200,:loop ; IF I <= 100 THEN GOTO :loop :exit ... ; Continue with next instruction.If your loop decrements the control variable, you'll have to use jnc (jump-if-not-carry) to detect an underflow and exit the loop.
The third type of For/Next loop can use 16-bit values for the start, stop and step values, and has a 16-bit control variable. It can increment or decrement from start to stop, and the value of the control variable may be used by the code inside the loop. It is a true For/Next loop.
Before we look at how to code this loop in assembly, let's consider what would happen if BASIC didn't include this instruction. Could programmers still write this kind of loop in BASIC? The answer is yes:
LET I = 1000 ' FOR I=1000... LOOP: ' Label for GOTO destination. pins = I ' Output 1000,998,996...100 I=I-2 ' Decrement I by 2. IF I >= 100 THEN LOOP ' ...TO 100This is pretty much how you handle more-complex types of For/Next loops in assembly. If you can avoid coding this kind of structure, do so. It uses the most program space and runs the most slowly of the options we've discussed. Here's how the example could be coded using subroutines from the If/Then and Let x = x+y sections of this book:
mov n1H,#03h ; n1 = 1000 (03E8h) mov n1L,#0E8h :loop mov rb,n1L ; rb = n1L. mov rc,n1H ; rc = n1H mov n2H,#0FFh ; n2 = -2 (two's complement: FFFEh) mov n2L,#0FEh call Add16 ; LET n1=n1-2 clr n2H ; n2 = 100 (0064h) mov n2L,#64h call Comp16 ; Compare n1 to n2. jmp pc+w ; W contains result of comparison: jmp :loop ; 0 means n1 = n2, so loop. jmp :loop ; 1 means n1 > n2, so loop. :exit ... ; 2 means n1 < n2, so exit loop.Note that this technique has the same flaw as the For/Next instructions of the PBASIC interpreter chip. What happens when the step is larger than the value (65535-end) for incrementing loops, or if the step is larger than the end value for decrementing loops? The value of n1 will roll over and the loop will execute more times than you expected. The problem is the same one we encountered previously with eight-bit variables. The cure is the same as for the eight-bit version; for incrementing loops add a jc (jump-if-carry) instruction right after the call to Add16 and set the destination to exit the loop. For decrementing loops, use a jnc (jump-if-not-carry) instead.
There is another type of loop that can eliminate the eccentricities of the others. This one maintains two control variables, a public one for the use of the code inside the loop, and a private one that counts iterations. Here is an example:
mov I,#50 ; Public variable I starts at 50. mov count,#6 ; Private counter for 6 loops :loop mov rb,I ; Output I ADD I,#33 ; I=I+33 djnz count,:loop ; Repeat 6 times.This is equivalent to FOR I = 50 to 248 STEP 33, but there's no chance of a flawed compare-and-jump allowing the loop to run away. It stops after six iterations. This approach is also handy when you want to let the value of the public control variable roll over. For example, if you wanted to code something like FOR I = 0 to 255 STEP 8, but execute the whole loop three times. In BASIC, you might nest that loop inside a FOR J = 1 to 3 loop. In assembly, you would just write a loop like the public/private one above. Assign count an initial value of 96, and keep adding 8 into I.
The PIC instruction set includes call and ret, which work much like PBASIC's Gosub and Return. The trouble is that the stack that call uses to store return addresses is only two levels deep in the PIC 16C5x series. This limits you to two nested calls. In other words, a called routine can call only one other. If that routine makes a call, the program will never find its way back the starting point. By contrast, PBASIC permits 16 nested Gosubs.
The demonstration program shows how you can break the two-level limit by having your program establish and manage a stack of return addresses for separate Gosub and Return routines. The calling program must put the address of the destination subroutine into sub, and the current contents of the program counter into w. It then jumps to Gosub, which stores the return-address information on a stack at the end of memory. When the subroutine is through, it jumps to Return. Return retrieves the return address from the stack, adjusts it to point to the line after the previous jmp Gosub, and goes there.
You can nest one subroutine for each byte of memory available. For example, if your program uses memory locations 8 through 19, then locations 20 through 31 are available for use as 12-level Return stack. There is a restriction in using Gosub. Since a program can read and store only the lower eight bits of the program counter, all of the subroutines must be in the lowest 256 instruction words of a program-memory page.

; GOSUB sub, return address (in w) ; Jumps to the address in the variable sub after storing the return address on a ; stack in general-purpose RAM. When the destination routine is finished, it ; jumps to a routine called Return, which fetches the return address off the stack, ; and jumps to the instruction following the one that invoked Gosub. ; The Gosub/Return mechanism requires exclusive use of the file-select ; register (fsr). If other parts of the program need to use the fsr, ; copy it to a variable before altering it and restore it when done. org 8 sub ds 1 ; Destination of Gosub. temp ds 1 ; Variables for delays temp2 ds 1 ; used in demo subroutines. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 start mov !rb, #0 ; All outputs for LEDs clr rb ; Turn LEDs off. mov fsr,#31 ; Point to end of memory mov sub,#One ; Destination: One. mov w,pc ; Save program counter. jmp Gosub ; Gosub One. mov rb,#15 ; LEDs on. :loop djnz temp,:loop ; Short delay. djnz temp2,:loop clr rb ; LEDs off. jmp $ ; Endless loop. Gosub mov indirect,w ; Return addr on stack. dec fsr ; Decrement stack pointer. mov w,sub ; Destination address to w. jmp w ; Go there. Return inc fsr ; Increment stack pointer mov w,++indirect ; w = return addr + 1. jmp w ; Jump to return addr. ; GOSUB (cont) ; Demonstration routines to show successful nesting of ; three Gosubs. One mov rb,#1 ; LED on. :loop djnz temp,:loop ; Short delay. djnz temp2,:loop clr rb ; LEDs off mov sub,#Two ; Destination: Two. mov w,pc ; Save program counter. jmp Gosub ; Gosub Two. jmp Return ; Return. Two mov rb,#3 ; LEDs on. :loop djnz temp,:loop ; Short delay. djnz temp2,:loop clr rb ; LEDs off mov sub,#Three ; Destination: Three. mov w,pc ; Save program counter. jmp Gosub ; Gosub Three jmp Return ; Return. Three mov rb,#7 ; LEDs on. :loop djnz temp,:loop ; Short delay. djnz temp2,:loop clr rb ; LEDs off jmp Return ; Return.
It is tempting to say that the assembly-language instruction jmp is equivalent to BASIC's GOTO and leave it at that. In fact, the Microchip version of GOTO is called goto, reinforcing this notion.
However, the layout of PIC program memory isn't quite that simple. The instructions jmp and goto accept a nine-bit address, giving them a range of 512 addresses (0 to 511). That's a good fit for the 16C54 and '55, which only have 512 words of program memory. But the '56 and '57 have 1024 and 2048 words, respectively. How do you go to an address beyond location 511?
One way is to do nothing. If your program is executing in a straight line and happens to cross that boundary, the program counter will simply increment into 512+ territory. The first jmp after that crossing will do a peculiar thing, though--it will land back in the previous 512 addresses.
The reason is that a jmp loads the lower nine bits of the program counter from the address supplied. It loads the upper two bits of the program counter from the page-select bits PA0 and PA1 of the status register. Normal incrementing of the program counter does not update these bits. In order to make sure that a jmp lands somewhere other than the first 512-word program-memory page, you must supply an 11-bit address or label to the Parallax long-jump instruction ljmp. It will assemble the bit set/clear instr uctions required to update PA0 and PA1.
If you need to make sure that jmps land in a particular memory page, you can use lset with an 11-bit address. The instruction will move bits 9 and 10 of the address into PA0 and PA1. Thereafter, any jmps executed will land in that memory page.
There is also a long-call instruction, lcall. It works the same way, but is subject to the same restriction as call. That is, its destination must be in the first 256 addresses of a program-memory page. In a nutshell, those are the mechanics of Goto in PIC assembly language. The Microchip data sheets on the various PIC devices have helpful diagrams of the program-memory map.
This is the equivalent of the assembly-language command setb pin, where pin is the bit address of the pin. PBASIC also changes the pin's data direction to output by writing a 0 to the appropriate bit of the TRIS register. (In assembly, a 0 in TRIS means makes a pin an output; a 1 makes it an input. This is the opposite of the BASIC Stamp.) The assembly-language example here changes only the output latch, not TRIS.
Of course, using setb means that you cannot choose which pin to write while the program is running. The pin address is coded as part of the machine-language instruction. The accompanying program listing is a port- and pin-independent version of setb. Write the pin number to pin, the port number to w, and call High.
High, like the other routines that accept pin and port arguments, requires the short table Pinz. Remember that tables must be located in the first 256 words of a 512-word program memory page.
One hint on using High: always assign the port number to w immediately before calling the routine. If you don't, some other instruction that uses w may change its contents, causing an error.

; HIGH port (in w), pin ; Outputs a high to the specified port and pin. org 8 pin ds 1 ; Pin number to set (0-7). ; Variables for the demo program--not required by High. p_index ds 1 ; Copy of pin number, port number. temp ds 1 ; Temporary variables for time delay temp2 ds 1 ; between outputs. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 ; Table to convert pin number (0-7) into bit mask (00000001b to 10000000b). Pinz jmp pc+w retw 1,2,4,8,16,32,64,128 ; The following demonstration code should show the value of being able to ; set a particular bit of a port based on a calculation performed while the ; program is running. As the variable p_index is incremented from 0 to 15, ; it is used to specify the pin and port to set using High. start mov !rb, #0 ; All outputs on rb. mov !rc, #0 ; All outputs on rc. clr rb ; Start with 0s on rb, rc (all LEDs off). clr rc clr p_index ; Clear p_index. :loop mov pin,#7 ; Copy three lsbs of p_index into AND pin,p_index ; variable pin. mov w,#1 ; IF p_index.3 = 0 snb p_index.3 ; THEN w = 1 mov w,#2 ; ELSE w = 2 call High call delay ; Wait a while between highs. inc p_index ; Next pin/port. sb p_index.4 ; IF p_index < 15 (rc.7) jmp :loop ; THEN :loop ELSE done done jmp $ ; Endless loop. ; HIGH (cont) ; To use the routine High, put the pin number into the variable pin ; and the port number into w. Then call High. High mov fsr,w ; Point to the port number. ADD fsr,#RA ; Add offset for port RA. mov w,pin call Pinz ; Get bit mask from the table. OR indirect,w ; Turn on the selected bit ret ; Delay routine for demonstration. Not required by High. delay djnz temp,delay ; Time delay to provide spacing djnz temp2,delay ; between highs. ret
PBASIC's If...Then statement allows you to compare two numbers up to 16 bits in length and make a decision (jump to a program address) based on the outcome of the comparison. Parallax assembly language offers the same capability through its compare-and-jump instructions (cja, cjae, cjb, cjbe...), but these are limited to 8-bit values.
The subroutine Comp16 compares 16-bit numbers and reports the results through the w register as follows:
0 n1 = n2 1 n1 > n2 2 n1 < n2Since it performs its comparisons through w, it does not alter n1 or n2.
To use Comp16 as a replacement for PBASIC's If...Then, use either a compare-and-jump instruction or Branch to process the result returned in w. See Branch for a method of handling compound logic expressions.

; COMPARE n1, n2 ; Compares two 16-bit numbers by subtracting one from the other and reports the ; outcome by a code in the w register. Does not alter n1 or n2. Uses the status ; register bit PA2 to preserve the z flag from subtraction of the LSBs. If you ; adapt this code for use in PICs other than 16C5x series, make sure that this ; bit is available. If it isn't, use a bit in one of the file registers instead. Z_ = PA2 ; Unused status bit as extra Z flag. org 8 n1H ds 1 ; MSB of n1. n1L ds 1 ; LSB of n1. n2H ds 1 ; MSB of n2. n2L ds 1 ; LSB of n2. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 start mov !rb,#0 ; Outputs for LEDs. mov n1H,#0C8h ; Compare 0C836h to mov n1L,#36h ; 2575h. mov n2H,#25h mov n2L,#75h call Comp16 ; Call the subroutine. mov rb,w ; Show result on LEDs jmp $ ; Endless loop ; Upon return, a code in w will indicate the outcome of the comparison. ; If n1 > n2 then w = 1. If n1 < n2 then w =2. If n1 = n2 then w = 0. Comp16 clrb Z_ ; Clear aux Z bit. mov w,n2L ; w = n2L subwf n1L,0 ; w = n1L-n2L snz ; Copy z bit to Z_. setb Z_ mov w,n2H ; If n1L-n2L underflowed, jc :cont ; then increment n2H (same mov w,++n2H ; as decrementing n1H). snz ; If n2H overflows, n1 must be < n2 retw 2 ; so return with 2 in w. :cont subwf n1H,0 ; w = n1H-n2H sc ; If n1H underflows, n1=n2. snz ; If both z and Z_ are set, retw 0 ; both subtractions resulted in 0, so :cont2 retw 1 ; n1=n2. Otherwise n1>n2.
In assembly language, converting a pin to input requires writing a 1 to the corresponding bit of the port's TRIS register. For example, to make pin 7 of port RB an input (and the rest of RB's pins outputs), you could use the instructions mov !rb, #10000000b. (In assembly, a 0 in TRIS means makes a pin an output; a 1 makes it an input. This is the opposite of the BASIC Stamp.)
When the PIC first resets, all I/O pins are set up as inputs by default. "Input" really has two meanings: (1) the ability to read the state of pins as set by circuitry outside the PIC, and (2) disconnection of the PIC's high-current drivers to present an almost-open circuit to the outside world. The second definition applies to instructions like Pwm that perform their output function, then disconnect to avoid interfering with the operation of other circuitry. In the case of Pwm, disconnecting the output dri ver allows a capacitor to retain the voltage level set by the burst of pulse-width modulation.
According to the PIC's manufacturer, Microchip Technology Inc., the TRIS registers can be corrupted by static discharges on the I/O pins. They recommend periodically rewriting the TRIS registers, just in case.
PBASIC allows you to add or subtract any two variables--bits, bytes, words, or a mixture--to get a 16-bit result. If a sum is larger than 16 bits, the highest-order bits are lost. If a difference is negative, it is a two's complement number.
The PIC's native instruction set includes 8-bit addition with a carry bit that is set when a sum exceeds 0FFh. This routine for 16-bit addition adds the LSBs first. If a carry occurs, it increments the MSB of the sum, then adds the MSBs.
While it is possible to code a separate subtraction routine in much the same way, it turns out to be easier to convert the number to be subtracted into a negative number instead. This is known as taking the two's complement of a number, and it just a matter of inverting the bits and adding 1. Two's complement numbers occur naturally in the kind of integer math that the PIC performs. Try subtracting 50 from 30 using the native PIC instruction sub. You'll get 236. Of course the answer should be -20. OK, take the two's complement of 20 using the Parallax instruction neg. You'll get 236.
So adding the two's complement of a number is the same as subtracting that number. This technique is used in other instructions throughout this book (e.g., Pause and Sleep) when it is more convenient to increment a negative number up to zero than to decrement a positive number down to zero. The reason is that the instructions inc and dec affect the zero bit, but not the carry bit. When a number is incremented, a carry occurs at the same time the number rolls over to zero. So the zero bit can be used to dete ct a carry when incrementing. This doesn't work when decrementing. The number hits zero one decrement before a carry would occur.
1010 0010 1100 0001Try adding and subtracting various combinations of n1 and n2 to see different results.

; ADD/SUBTRACT n1, n2 ; Add two 16-bit numbers to produce a 16-bit result, permitting overflow. ; Add the two's complement of one 16-bit number to another 16-bit number ; to perform subtraction, permitting underflow. ; Addition: n1 = n1 + n2 ; Subtraction: n1 = n1 - n2 org 8 n1H ds 1 ; MSB of n1. n1L ds 1 ; LSB of n1. n2H ds 1 ; MSB of n2. n2L ds 1 ; LSB of n2. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 start mov !rb,#0 ; Outputs for LEDs to mov !rc,#0 ; display 16-bit result. mov n1H,#0C8h ; Problem: C836h-2575h mov n1L,#36h mov n2H,#25h mov n2L,#75h call Sub16 ; n1 = n1 - n2 mov rb,n1L ; Show result in binary on mov rc,n1H ; LEDs connected to ports. jmp $ ; Endless loop Sub16 NOT n2H ; Take two's complement NEG n2L ; of n2. If zero, n2L snz ; overflowed, so carry inc n2H ; into n2H. Add16 ADD n1L,n2L ; Add the LSBs. If carry, snc ; increment MSB of sum. inc n1H ADD n1H,n2H ; Add the MSBs ret
PBASIC allows you to divide any two variables--bits, bytes, words, or a mixture--to get a 16-bit result or remainder.
This routine duplicates that capability in assembly language. It uses a binary interpretation of the long-division method you were probably taught in school. It shifts the divisor left until there is a 1 in the msb. It then attempts to subtract this number from the dividend.
If the result of the subtraction is positive, the routine puts a 1 into the lsb of the quotient, shifts the quotient left, shifts the divisor right, and repeats the operation on what remains of the dividend. If the result of the subtraction is negative, the routine undoes the subtraction by adding the divisor and dividend back together, puts a 0 into the lsb of the quotient, shifts the quotient left, shifts the divisor right, and repeats the operation.
It may require a walk through the routine with paper and pencil (or the simulator) to grasp how this works. When the routine finishes, the 16-bit variable consisting of qH and qL holds the quotient or result of the division. The variable consisting of topH and topL holds the remainder.
Division by 0 is illegal, so the routine starts by checking for this. If division by 0 is attempted, the routine aborts and returns the error code 0FFh (255) in the w register. Otherwise, it returns with 0 in w. Your program should either prevent division by 0 or be prepared to act on the error code.
0000 0000 0101 0000Try dividing various values to see different results. Also, modify the program to display remainders (stored in the variables topH and topL) on the LEDs.

; DIVIDE ; Divide one 16-bit number into another, returning the 16-bit result and ; the remainder. Upon entry, the top of the division fraction (the dividend) ; must be in topH and topL, and the bottom (divisor) in btmH and btmL. ; If division by zero is attempted, the routine will return immediately with ; the error code of 0FFh in w. Otherwise, it will perform the division, leaving ; the remainder in topH and topL and the result (quotient) in qH and qL. ; Upon return from a successful division, w contains 0. org 8 topH ds 1 ; MSB of top of fraction. topL ds 1 ; LSB of top of fraction. btmH ds 1 ; MSB of bottom of fraction. btmL ds 1 ; LSB of bottom of fraction. qH ds 1 ; MSB of quotient. qL ds 1 ; LSB of quotient. count ds 1 ; temporary counter index ds 1 ; temporary counter ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 start mov !rb,#0 ; Set to outputs to display mov !rc,#0 ; results on LEDs. mov topH,#33h ; Problem: divide mov topL,#0Ah ; 330Ah by 00A3h. mov btmH,#0h mov btmL,#0A3h call divide mov rb,qL ; Display quotient in binary mov rc,qH ; on LEDs connected to rb and rc. jmp $ ; Endless loop Divide mov w,btmH ; Check for division by 0. OR w,btmL snz retw 255 ; Error code= 255: return. mov count,#1 ; Otherwise, initialize variables clr index ; for the division. clr qH clr qL :sh_loop jb btmH.7,:d1 ; Shift divisor left clc ; until msb is in rl btmL ; btmH.7. rl btmH ; count = no. of shifts+1. inc count jmp :sh_loop :d1 clc rl qL ; Shift quotient left. rl qH sub topL,btmL ; top = top - btm. jc :d2 ; If top - btm < 0 then sub topH,#1 ; top = top + btm jc :d2 ; The idea is to do the inc topH ; the subtraction and comparison add topL,btmL ; (top > btm?) in one step. goto :reentr ; Then, if btm > top, undo :d2 sub topH,btmH ; the subtraction by adding sc ; top and btm back together goto :less setb qL.0 :reentr clc rr btmH rr btmL djnz count,:d1 ret ; Return w/ remainder in top ; and result in q. :less ADD topL,btmL ; btm > top, so snc ; undo the subtraction by inc topH ; adding them back together. ADD topH,btmH goto :reentr
PBASIC allows you to multiply any two variables together--bits, bytes, words, or a mixture--to get a 16-bit product. If the product is larger than 16 bits, the highest-order bits are lost.
This isn't as much of a handicap as it might sound. Users of the BASIC Stamp have managed to get excellent results by carefully designing their programs to avoid overflow. The benefit of working within this limitation is that the routine required to do this sort of math is compact and reasonably fast.
Multiplication of binary numbers works just like the long-form multiplication you learned in school, only it's much easier. Instead of multiplication tables, all you need is this general rule: n x 0 = 0; and n x 1 = n. Arrange the numbers in the traditional way, and you're ready to go:

1011 0000 1111 0010Try multiplying various values to see different results.

; MULTIPLY multiplier, multiplicand ; Multiply two 16-bit numbers into a 16-bit product, permitting overflow. org 8 mulcH ds 1 ; MSB of multiplicand. mulcL ds 1 ; LSB of multiplicand. mulpH ds 1 ; MSB of multiplier. mulpL ds 1 ; LSB of multiplier. prodH ds 1 ; MSB of product. prodL ds 1 ; LSB of product. index ds 1 ; temporary counter ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 start mov !rb,#0 ; Outputs for LEDs to mov !rc,#0 ; display 16-bit result. mov mulcH,#0 ; Problem: multiply mov mulcL,#0Bh ; 0Bh by 1016h mov mulpH,#10h mov mulpL,#16h call multiply mov rb,prodL ; Show result in binary on mov rc,prodH ; LEDs connected to ports. jmp $ ; Endless loop Multiply clr prodH ; Clear product to make clr prodL ; way for new calculation. mov index,#16 ; Number of bits to calc. :loop clc rl prodL ; Shift product left. rl prodH clc rl mulcL ; Shift multiplicand left. rl mulcH jnc :skip ; If carry, add multiplier ADD prodL,mulpL ; to the product. Else skip. snc ; 16-bit addition: prod+mulp inc prodH ADD prodH,mulpH :skip djnz index,:loop ret
Lookdown is useful for interpreting data that is not organized in any particular pattern. For example, say you were constructing an instrument that could be controlled by one-letter commands received over a serial connection, and your commands were A, G, Q and X. You could use Lookdown to convert these letters to the values 0 through 3 for processing by your program, or to jump into a Branch table.
Lookdown takes two inputs, a table to search and a variable key to search for. On return, key is unchanged, the variable index holds the position number of key in the table (if it was found), and w holds an error code. The error codes returned in w are as follows:
0 = No error. 1 = Key is not in the table.The table that Lookdown searches must be set up so that its first element is the number of entries. For instance, if a table had 3 searchable entries, element 0 would be 3; element 0 itself is not included in the count. Duplicate entries must be avoided, since Lookdown returns only one answer.
The position number that Lookdown reports is adjusted so that 0 = the first searchable entry, 1 = the second, and so on. This makes it compatible with PBASIC. If you want Lookdown to number entries starting with 1 instead of 0, remove the instruction dec index from the end of the routine. If you make this change, move the label :done next to the ret instruction.

; LOOKDOWN key, table ; Searches a table for an element that matches key. If a match ; is found, Lookdown returns its position in the table in the ; variable index and a 0 in the w register. If no element matches key, ; Lookdown returns a 1 in the w register as an error code. org 8 key ds 1 ; Element to find. index ds 1 ; Position of key in table. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 start mov !rb,#0 ; Output to show index on LEDs. mov key,#'G' ; Search for byte corresponding to call Lookdown ; ASCII G and return index no. mov rb,index ; Show index on LEDs hooked to RB. jmp $ ; Endless loop. ; The table to be searched must include the number of searchable elements ; as element no. 0 (the very first element in the table). Table jmp pc+w retw 10 ; No. of searchable elements. retw 'ABCDEFGHIJ' ; Table of ASCII characters. ; Locate table element corresponding to key. ; Return with the corresponding element number in index. Key is not ; altered by Lookdown. Note that when the element is found, :done ; decrements index so that the first element = 0, second = 1... ; just as with the PBASIC instruction. Lookdown clr w ; Retrieve element 0. call Table mov index,w ; Copy table length to index. :loop call Table ; Retrieve element. subwf key,0 ; W = key - W snz jmp :done ; If w = 0, we have a match. dec index ; If not, try next lower element. mov w,index ; Put index into w for table. sz ; If zero, element is not in table. jmp :loop ; Try next lower element retw 1 ; Error code: element not in table. :done dec index ; index=index-1 to match PBASIC. ret ; Element found: return a 0 in w.
A classic application for Lookup is to convert the numbers 0 through 9 (or 0 through F in hexadecimal) into patterns for seven-segment displays. Throughout this book, you will see other applications, such as the table Pinz that some routines use to change a number in the range of 0 through 7 into a byte with a 1 in the corresponding position for use as a bit mask.
Lookup tables are an inherent PIC capability made possible by the retw instruction, which stands for "return with literal value in w." A table consists of a way to select from a list of retws. The instruction jmp pc+w takes care of this. Since the program counter always points to the next instruction, a 0 in w selects the first retw from the list; a 1 the second; a 2 the third, and so on.
The Parallax assembler allows great flexibility in formatting data for tables. You can type one retw n per line, or use the shorthand retw n1,n2,n3...For tables of text, you can type retw 'Text data in retw table'. The assembler will turn this into a series of retws with the ASCII codes for each character of the text. For an example of such a table, see the program listing for Lookdown.
A couple of cautions about using tables. First, your program must ensure that the number in w never exceeds the maximum entry in the table. If it does, program execution will jump completely over the table. Second, tables must be in the first 256 words of a 512-word program-memory page because of the way the PIC's computed-jump mechanism works.

; LOOKUP index (in w), table ; Finds the table entry corresponding to index (in w) and ; returns it in w. This demonstration uses a table to map the ; numbers 0 through F hex to patterns on a seven-segment LED ; display. It assumes that the display is connected with segment ; a to rb.0, b to rb.1... and g to rb.6 through current-limiting ; resistors of 220 ohms minimum. When the program runs, it counts ; upward 0, 1, 2, 3...through F, then begins the count again at 0. org 8 count ds 1 ; Counter variable for demo. temp ds 1 ; Variables for delays temp2 ds 1 ; used in demo subroutines. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 Lookup jmp pc+w ; Jump to entry spec'd by w. retw 63,6,91,79 ; 0, 1, 2, 3 retw 102,109,125 ; 4, 5, 6 retw 7,127,103 ; 7, 8, 9 retw 119,124,57 ; A, B, C retw 94,121,113 ; D, E, F start mov !rb, #0 ; All outputs for LEDs clr rb ; All LEDs off to begin. clr count ; Start count at 0. :loop mov w,count ; Put number to look up into w. AND w,#00001111b ; Strip off high bits. call Lookup ; Call the table. mov rb,w ; Write pattern in w to LEDs. :wait djnz temp,:wait ; Short delay. djnz temp2,:wait inc count ; count = count + 1 jmp :loop ; Do it again.
This is the equivalent of the assembly-language command clrb pin, where pin is the bit address of the pin. PBASIC also changes the pin's data direction to output by writing a 0 to the appropriate bit of the TRIS register. (In assembly, a 0 in TRIS means makes a pin an output; a 1 makes it an input. This is the opposite of the BASIC Stamp.) The assembly-language example here changes only the output latch, not TRIS.
Of course, using clrb means that you cannot change the pin being written while the program is running. The pin address is coded as part of the machine-language instruction. The accompanying program listing is a port- and pin-independent version of clrb. Write the pin number to pin, the port number to w, and call Low.
Low, like the other routines that accept pin and port arguments, requires the short table Pinz. Remember that tables must be located in the first 256 words of a 512-word program memory page.
One hint on using Low: always assign the port number to w immediately before calling the routine. If you don't, some other instruction that uses w may change its contents, causing an error.

; LOW port (in w), pin ; Outputs a low to the specified port and pin. org 8 pin ds 1 ; Pin number to set (0-7). ; Variables for the demo program--not required by Low. p_index ds 1 ; Copy of pin number, port number. temp ds 1 ; Temporary variables for time delay temp2 ds 1 ; between outputs. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 ; Table to convert pin number (0-7) into bit mask (00000001b to 10000000b). Pinz jmp pc+w retw 1,2,4,8,16,32,64,128 ; The following demonstration code should show the value of being able to ; clear a particular bit of a port based on a calculation performed while the ; program is running. As the variable p_index is incremented from 0 to 15, ; it is used to specify the pin and port to clear using Low. start mov !rb, #0 ; All outputs on rb. mov !rc, #0 ; All outputs on rc. mov rb,#255 ; Start with 1s on rb, rc (all LEDs on). mov rc,#255 clr p_index ; Clear p_index. :loop mov pin,#7 ; Copy three lsbs of p_index into AND pin,p_index ; variable pin. mov w,#1 ; IF p_index.3 = 0 snb p_index.3 ; THEN w = 1 mov w,#2 ; ELSE w = 2 call Low call delay ; Wait a while between lows. inc p_index ; Next pin/port. sb p_index.4 ; IF p_index < 15 (rc.7) jmp :loop ; THEN :loop ELSE done done jmp $ ; Endless loop. ; To use the routine Low, put the pin number into the variable pin ; and the port number into w. Then call Low. Low mov fsr,w ; Point to the port number. ADD fsr,#RA ; Add offset for port RA. mov w,pin call Pinz ; Get bit mask from the table. NOT w ; Invert it so there's a 0 in selected bit. AND indirect,w ; Turn off the selected bit ret ; Delay routine for demonstration. Not required by Low. delay djnz temp,delay ; Time delay to provide spacing djnz temp2,delay ; between highs. ret
The PIC has the built-in capability to turn itself off, reducing current draw to 3 µA (other than any loads driven by I/O pins). Even short naps at this reduced current draw can extend battery life tremendously. To use Nap, put the length of nap desired (0 to 7) into w and call the subroutine. Nap sets bit 3 of w to assign the prescaler to the watchdog timer, and then writes w to the option register. The routine then puts the PIC to sleep.
Later, when the watchdog times out, the PIC resets. It jumps to start (specified to execute when the PIC comes out of reset by the reset directive). Code in start figures out the cause of the reset by examining the timeout (to) and power-down (pd) bits of the status register. If the reset was caused by a watchdog timeout awakening the PIC out of sleep (to = 0, pd = 0), a ret is executed, returning the program to the line following call Nap.
As you can see from the example listing, napping requires more than just the subroutine to work. The device directive must include wdt_on to enable the watchdog timer. The code at the beginning of the program must examine the to and pd bits and decide what to do. And, although it's not shown in the example, the program must clear the watchdog timer (clr wdt) frequently enough to prevent it from interrupting the running program. How frequently depends on the value in the prescaler and whether or not the rest of the program uses the prescaler with the RTCC.
The safest answer is to clear the watchdog at least every 9 ms. Then, no matter how the option register is set up or what the actual speed of the watchdog's oscillator is, the wdt will never overflow. Another issue concerns what to do about the wdt-invoked reset and its effect on the I/O pins. During a reset, all pins are set to input. If the PIC is driving a load during its nap, there will be a brief glitch as the TRIS registers are reset. It is probably best not to drive loads directly during naps, but to rely on pullup or pulldown resistors to do the job instead.
Finally, remember that all computed jumps and called subroutines must be in the first 256 words of a program-memory page. In this case, that would include both Nap and the decision-making code in start.

; NAP period (in w) ; Puts the PIC to sleep for a short period of time. ; The length of the nap may be in the range of 0 to 7 and is passed ; to the subroutine in the w register. The length of nap (in seconds) is ; 2^w * 18 ms, as follows: ; w = 0: nap = 18 ms ; w = 1: nap = 36 ms ; w = 2: nap = 72 ms ; w = 3: nap = 144 ms ; w = 4: nap = 288 ms ; w = 5: nap = 576 ms ; w = 6: nap = 1152 ms ; w = 7: nap = 2304 ms ; The 18-ms value is very approximate. It is based on a supply ; voltage of 5 Vdc and an operating temperature of 77 degrees F. ; At higher temperatures and lower supply voltages, it can be much ; longer--even double--the nominal 18 ms. Likewise, at lower ; temperatures and higher supply voltages it can fall to half. ; Note that Nap uses one level of the call/return stack. ; Device data and reset vector: Note that the watchdog timer is ON. device pic16c55,xt_osc,wdt_on,protect_off reset start org 0 Nap OR w,#8 ; Set option.3 to assign prescaler mov option,w ; Lower 3 bits are prescale rate. sleep ; Go to sleep start mov !rb,#0 ; Make RB all outputs for LEDs. clr w ; At the beginning of the program, snb pd ; we set up a Branch-type routine OR w,#1 ; to take action based on the snb to ; power-down (pd) and timeout (to) OR w,#2 ; bits. jmp pc+w ; | to | pd | ret ; | 0 | 0 | wdt wake: return. jmp wdt_fail ; | 0 | 1 | wdt timeout: handle it. jmp mclr_pin ; | 1 | 0 | mclr wake: handle it. jmp main ; | 1 | 1 | pwr on: main. ; This code executes when power is first applied to the PIC (after start). ; It's a loop in which the PIC takes a 1.152-second nap, then inverts ; port rb. The result is that LEDs connected to RB flash at the rate of the ; nap period. main mov w,#6 ; Set up a 1152-ms nap. call Nap ; Snooze for >1 second. XOR rb,#255 ; Wake up and invert RB. jmp main ; Do it again. ; In an actual application, this code would contain instructions for dealing ; with a reset pulse waking the PIC out of its nap. Here, we have an empty ; routine that just returns to the main loop. mclr_pin ret ; In an actual application, this code would contain instructions for dealing ; with a watchdog-timer timeout. Here, we have an empty routine that just ; returns to the main loop. wdt_fail ret
In assembly language, converting a pin to output requires writing a 0 to the corresponding bit of the port's TRIS register. For example, to make pin 7 of port RB an output (and the rest of RB's pins inputs), you could use the instructions mov !rb, #01111111b. (In assembly, a 1 in TRIS means makes a pin an input; a 0 makes it an output. This is the opposite of the BASIC Stamp.)
When the PIC first resets, all I/O pins are set up as inputs by default. Pins that are to be used as outputs must be set up by writing 0s to the appropriate TRIS registers.
According to the PIC's manufacturer, Microchip Inc., the TRIS registers can be corrupted by static discharges on the I/O pins. They recommend periodically rewriting the TRIS registers, just in case.
Precise time delays are an important part of most control program, since they allow the controller to synchronize with slower, real-world events.
Pause accepts a 16-bit number in hiB and lowB, and puts the controller into a do-nothing loop for that number of 1000-instruction-cycle units. With a 4-MHz clock (as in the BASIC Stamp) this amounts to time delays ranging from 1 to 65,535 milliseconds.
The routine requires a little processing time in addition to the internal delay loop, so the actual delay produced will always be 16 instruction cycles longer than specified. This amounts to an error of less than 2 percent for a 1-unit delay, and 0.24 parts per million for a 65,535-unit delay.

; PAUSE time ; A general-purpose delay routine that puts the PIC into a do-nothing ; loop for a 16-bit number of milliseconds (1 to 65535) at 4 MHz. Requires ; 16 cycles of overhead for call, return and other processing. org 8 hiB ds 1 ; MSB of time. lowB ds 1 ; LSB of time. temp ds 1 ; temporary counter for Pause ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 start mov !rb, #0 ; Output through RB. mov hiB,#3 ; Length of delay = mov lowB,#0E8h ; 3E8h (1000 ms @ 4MHz). XOR rb,#255 ; Toggle LED(s). call Pause ; Wait 1 second. jmp start ; Do it again. ; Upon entry, variables hiB and lowB hold the MSB and LSB of the ; number of milliseconds (1 to 65535) to delay. Pause NOT hiB ; Take twos complement NEG lowB ; of the 16-bit counter snz ; If zero, lowB overflowed, inc hiB ; so carry into hiB. :load mov temp,#248 ; Set up for 1-ms loop. :loop nop ; Do nothing. djnz temp,:loop ; Do more nothing. jmp $+1 ; 2-cycle "nop" inc lowB ; lowB = lowB+1. snz ; Overflow in lowB? incsz hiB ; Yes: hiB=hiB+1, overflow?. jmp :load ; No overflow: do it all again. ret ; Overflow. Return to caller.
PBASIC's Pot command allows you to measure resistance without an external analog-to-digital converter. All that's required is a capacitor to set up a timing circuit as shown below.

Once the capacitor is fully charged, the program discharges it a little at a time, periodically changing the pin to input to determine whether it's low yet. If not, Pot increments a counter and tries again until the pin goes low.
You can adapt this technique to practically any combination of R and C. However, if C is greater than 0.1µF, it's a good idea to add a fixed series resistor of 220 ohms or so between the I/O pin and the pot itself. This limits the maximum current through the I/O pin in the event that the pot resistance is near 0 and the capacitor is initially discharged.
The PBASIC Pot routine includes a scale factor that can be used to calibrate a particular combination of R and C to produce full-range results (0 to 255). That feature undoubtedly draws on built-in math routines. You may not need scaling capabilities, so the demonstration here scales the Pot results by shifting them right. This is equivalent to integer division by two. It doesn't provide exactly full-scale results, but it keeps the routine compact.

; POT ; This program shows a technique for reading resistance. The pot input pin can ; only be changed during assembly, not once the program is running. org 8 temp ds 1 ; Temporary counter for delay. potL ds 1 ; Low byte of Pot measurement. potH ds 1 ; High byte of Pot measurement. pot = ra.0 ; Pin assigned to pot. out_pot = 0 ; Value to set ra.0 to output for pot. in_pot = 1 ; Value to set ra.0 to input for pot. device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 start mov !ra,#out_pot ; RA.0 to output. mov !rb,#0 ; All outputs for LEDs. setb pot ; Start charging cap. mov potL,#8 ; Set up for 6-ms delay: 8 trips :wait djnz temp,:wait ; through outside loop x 256 djnz potL,:wait ; x 3 cycles for inside loop = 6.1 ms. clr potL clr potH mov !ra,#in_pot ; Get input from pot. clrb pot ; Clear output latch for pot bit. :loop jnb pot,:done ; IF pot = 0 THEN :done mov !ra,#out_pot ; Discharge cap a little. mov !ra,#in_pot ; Change pot pin to input. inc potL ; Incerement potL. snz ; If zero then potL overfolowed, so incsz potH ; increment potH. If it overflows, exit. jmp :loop ; Overflowed? No: loop. :done mov temp,#2 ; Divide 16-bit number represented by :div clc ; potH and potL by 2^temp (in this case, rr potH ; 2^2 or 4). This scales the pot value to rr potL ; fit within an 8-bit range. djnz temp,:div mov rb,potL ; Show the outcome on LEDs. jmp start ; Do it again.
Tachometers, duty-cycle meters, capacitor checkers, and precise interval timers can all be implemented with Pulsin.
To use Pulsin, you must set the desired pin to input and supply three arguments: the pulse direction in state (0 = 1-to-0, 255 = 0-to-1), the pin number (0 to 7) in pin, and the port number (0 = RA, 1 = RB, etc.) in w. When Pulsin returns, the 16-bit pulse width will be in hiB and lowB, and an error code in w. State and pin will have been modified by Pulsin for its own use, so your program should keep copies of these variables if needed.
The error codes returned in w are as follows:
One hint on using Pulsin: always assign the port number to w immediately before calling the routine. If you don't, some other instruction that uses w may change its contents, causing an error.

; PULSIN port (in w), pin, state ; Measures the width of an input pulse in 10-cycle (10 us at 4 MHz) units. ; Returns a 16-bit (0 to 65,535) value. If no pulse occurs within ; 65,535 10-cycle intervals, the routine returns an error code in w. ; Routine is edge triggered. If the input pin is already in the target state ; when the routine begins, it will wait for the state to change, then wait ; for the specified edge. The routine waits 65,535 x 10 cycles for ; each edge, so it normally returns within 0.65535 seconds (4 MHz). ; However, if each transition is delayed by close to the full 65,535 x 10 ; cycles, the routine can take nearly 2 seconds to return (4 MHz). org 8 pin ds 1 ; Pin number to sample (0-7). state ds 1 ; Trigger: 255 = 0-to-1 transition. hiB ds 1 ; MSB of measurement. lowB ds 1 ; LSB of measurement. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 ; Table to convert pin number (0-7) into bit mask (00000001b to 10000000b). Pinz jmp pc+w retw 1,2,4,8,16,32,64,128 start mov !rb,#0 ; Outputs to display result on LEDs. mov !rc,#0 mov !ra, #1111b ; All inputs mov state,#0 ; Negative pulse. mov pin,#2 ; Pin 2. mov w,#0 ; Port ra. call Pulsin mov rb,lowB ; Write Pulsin result to ports RB mov rc,hiB ; and RC for viewing on LEDs. jmp start ; Do it again. ; Upon entry, the desired pin must already be set up as an input. ; The w register contains a number representing the input port (0-2) for ; RA through RC. Variable pin contains the pin number (0-7). Variable state ; contains 255 for a 0-to-1 (positive) pulse, or 0 for a 1-to-0 (negative) pulse. ; Upon exit, the 16-bit count is in variables lowB and hiB. ; Variable pin contains the mask bit used by pulsin (not the pin number ; anymore, but a value with a 1 in the pin position), and state has been ; ANDed with pin. W contains the error code: 0= no error; 1= pin in ; already in target state when Pulsin was called--timeout waiting ; for transition; 2= timeout waiting for initial edge; 3= timeout waiting ; for final edge. Pulsin clr lowB ; Clear the 16-bit counter. clr hiB mov fsr,w ; Point to the port number. add fsr,#RA ; Add offset for port RA. mov w,pin ; Get bit mask from the table. call Pinz mov pin,w ; Put the mask into pin. AND state,w ; Mask extra bits out of state. :Hold mov w,indirect ; Sample the port. AND w,pin ; Mask off bits other than pin. XOR w,state ; Check pin against state. jnz :doneH ; If pin <> state, set up for edge. inc lowB ; Otherwise, lowB = lowB+1. snz ; Overflow in lowB? incsz hiB ; Yes: hiB=hiB+1, watch for overflow. jmp :Hold ; Sample the pin again. retw 1 ; Overflow in hiB? Timed out: return. :doneH clr lowB clr hiB :Edge1 mov w,indirect ; Sample the port. AND w,pin ; Mask off bits other than pin. XOR w,state ; Check pin against state. jz :doneE ; If pin = state, wait until change. inc lowB ; Otherwise, lowB = lowB+1. snz ; Overflow in lowB? incsz hiB ; Yes: hiB=hiB+1, watch for overflow. jmp :Edge1 ; Sample the pin again. retw 2 ; Overflow in hiB? Timed out: return. :doneE clr lowB clr hiB :Edge2 mov w,indirect ; Sample the port. AND w,pin ; Mask off bits other than pin. XOR w,state ; Check pin against state. jnz :done ; If pin <> state, we're done. inc lowB ; Otherwise, lowB = lowB+1. snz ; Overflow in lowB? incsz hiB ; Yes: hiB=hiB+1, watch for overflow. jmp :Edge2 ; Sample the pin again. retw 3 ; Overflow in hiB? Timed out: return. :done ret ; Done: pulsewidth in hiB, lowB.
Pulsout can produce brief clock pulses to blip the enable lines of LCD modules and logic devices, or wide-range pulses to drive positioning servos. It differs from the Pwm command in two ways: it produces only one pulse each time it is called, and duty cycle is controlled by the ratio of the pulse width to the interval between pulses (if it is called repeatedly).
To use Pulsout, you must set the desired pin to output and supply three arguments: the desired length of the pulse in 10-instruction cycle units in the 16-bit variable consisting of hiB and lowB, the pin number (0 to 7) in pin, and the port number (0 = RA, 1 = RB, etc.) in w.
The type of pulse generated by Pulsout depends on the state of the pin's output latch when the routine is called. If pin is initially 1, Pulsout will invert it to 0 for the specified period of time, then restore it to 1. If it's initially 0, it will be set to 1 for the specified time, then cleared to 0.
Upon return, the contents of hiB and lowB will have been cleared, so keep copies of these values if they are required elsewhere in your program.
Half of Pulsout's 10-cycle delay is produced with nop instructions. If you want a 5-cycle version of Pulsout, remove the nops.
Pulsout, like the other routines that accept pin and port arguments, requires the short table Pinz. Remember that tables must be located in the first 256 words of a 512-word program memory page. One hint on using Pulsout: always assign the port number to w immediately before calling the routine. If you don't, some other instruction that uses w may change its contents, causing an error.

; PULSOUT port, pin, time ; Generates an output pulse in 10-cycle (10 us at 4 MHz) units, ; based on a 16-bit (1 to 65,535) value. The pulse is the reverse ; of the pin's state when pulsout is called. For instance, if the ; specified pin is initially 1, Pulsout will invert it to make a ; negative-going pulse. org 8 hiB ds 1 ; MSB of time. lowB ds 1 ; LSB of time. pin ds 1 ; Pin number to pulse (0-7). temp ds 1 ; Temporary variables for time delay temp2 ds 1 ; between pulses ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 ; Table to convert pin number (0-7) into bit mask (00000001b to 10000000b). Pinz jmp pc+w retw 1,2,4,8,16,32,64,128 start mov !ra, #0 ; All outputs. clr ra ; Start with 0s. mov lowB,#10 ; 10 x 10-cycle pulse clr hiB mov pin,#2 ; Pin 2. mov w,#0 ; of port ra. call Pulsout ; Pulse pin high for 100 cycles. call delay ; Wait a while between pulses. jmp start ; Do it again. ; Upon entry, the desired pin must already be set up as an output. ; The w register contains a number representing the output port (0-2) for ; RA through RC. Variable "pin" contains the pin number (0-7). The variables ; hiB and lowB are the MSB and LSB of the time argument. Pulsout mov fsr,w ; Point to the port number. add fsr,#RA ; Add offset for port RA. mov w,pin call Pinz ; Get bit mask from the table. mov pin,w ; Put the mask into pin NOT hiB ; Take twos complement NEG lowB ; of the 16-bit counter snz ; If zero, lowB overflowed, inc hiB ; so carry into hiB. XOR indirect,pin ; Invert the selected pin. ; The main timing loop. Remove the nops for 5-cycle resolution. :loop jmp $+1 ; Two-cycle "nop." jmp $+1 ; Two-cycle "nop." nop inc lowB ; lowB = lowB+1. snz ; Overflow in lowB? incsz hiB ; Yes: hiB=hiB+1, watch for overflow. jmp :loop ; If not overflow, do it again. XOR indirect,pin ; Invert pin (back to initial state). ret ; Delay routine for demonstration. Not required by Pulsout. delay djnz temp,delay ; Time delay to provide spacing djnz temp2,delay ; between pulses. ret
Many designers think "hardware" when they need to convert a digital value to an analog voltage. For many applications, a firmware DAC like the PBASIC command Pwm (pulse-width modulation), can do the job with just an external resistor and capacitor.
The principle is simple. If the RC circuit shown here is connected to a square wave source that is high 50% of the time, the voltage across the capacitor will be 2.5 volts (assuming high = 5 volts and low = 0 volts). If the waveform is high 10% of the time, the voltage will be 0.5 volts.

Pwm's output is not a high pulse of x duration followed by a low of y duration. It is a jumbled series of highs and lows whose overall proportion is x/y. That's because Pwm relies on the repeated addition of the duty value into an accumulator to generate carries. After each addition, it copies the carry bit to the output. This looks like a mess when viewed on an oscilloscope.
To use Pwm, set the desired pin to output, put the port number (0 to 2 for RA through RC) into w, put the pin number into the variable pin, and the duty cycle into the variable duty. Then call Pwm. Pwm emits 256 pulses at the specified duty cycle and then returns. At this time, your program should change the pin from output to input. This disconnects the pin from the RC integrator and prevents the steady state of the pin from altering the charge on the capacitor. The example program uses the duty cycle to vary the brightness of an LED. No RC integrator required.
Pwm, like the other routines that accept pin and port arguments, requires the short table Pinz. Remember that tables must be located in the first 256 words of a 512-word program memory page. One hint on using Pwm: always assign the port number to w immediately before calling the routine. If you don't, some other instruction that uses w may change its contents, causing an error.

; PWM port (in w), pin, duty ; Generates a bit stream of 256 1s and 0s with the specified ; duty cycle. If this bit stream is run through a simple RC ; integrator, the result is an analog voltage output of ; (duty/255) * (PIC supply voltage). For example, if duty is ; 100 and the supply is 5 volts, then the integrated output of ; pwm would be (100/255) * 5 = 1.96 volts. In many applications, ; pwm output should be buffered with a unity-gain op-amp ; circuit. In those cases, the pwm output pin should be set up ; as an output when Pwm is called, then immediately switched to ; input when Pwm is done. This prevents the steady-state output ; of the pin from affecting the voltage set by Pwm. org 8 duty ds 1 ; Pwm duty cycle. acc ds 1 ; Pwm accumulator. index ds 1 ; Temporary counter for pwm. pin ds 1 ; Pin number to pulse (0-7). ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 ; Table to convert pin number (0-7) into bit mask (00000001b to 10000000b). Pinz jmp pc+w retw 1,2,4,8,16,32,64,128 start mov !ra, #0 ; All outputs. clr ra ; Start with LED off. clr duty ; Initial brightness = 0. clr index ; Clear loop counter for pwm. :loop mov pin,#2 ; Pin 2. mov w,#0 ; of port ra. call pwm ; Send pwm to LED. inc duty ; Turn up brightness. jmp :loop ; Endless loop ; Upon entry, the desired pin must already be set up as an output. ; Variable "pin" contains the pin number (0-7). The w register contains a ; number representing the output port (0-2) for RA through RC. The variable ; duty contains the desired duty cycle from 0 to 255. Pwm mov fsr,w ; Point to the port number. add fsr,#RA ; Add offset for port RA. mov w,pin call Pinz ; Get bit mask from the table. mov pin,w ; Put the mask into pin. :loop add acc,duty ; Let acc = acc + duty mov w,pin snc ; IF carry THEN pin = 1 OR indirect,w ; ELSE pin = 0. mov w,/pin sc AND indirect,w djnz index,:loop ; Repeat 256 times. ret
Games require a source of random numbers to keep the play surprising and exciting. Streams of pseudorandom bits can be used to generate white noise or interpolate between A/D converter measurements. Random simulates a hardware pseudorandom sequence generator made up of a shift register and three exclusive-OR (XOR) gates wired up as follows:

From one call to the next, the pattern inherent in Random is very apparent. A number is doubled, truncated to 16 bits (or 8, if you're using only one of the variables), and may or may not have a 1 added to the result. If Random is used to control patterns of lights or sounds, this pattern may be objectionable. In those cases, try to borrow an element of uncertainty from the outside world. For example, continuously call Random while waiting for the user to press a switch. This is analogous to BASIC programs on PCs that seed the random number generator with the current contents of the timer.

; RANDOM ; Generates a pseudorandom number. Works best if called from a loop so that ; the value of its workspace variable (consisting of lowB and hiB) is ; constantly stirred. org 8 hiB ds 1 ; MSB of 16-bit random number. lowB ds 1 ; LSB of 16-bit random number. temp ds 1 ; Temporary counter for delay. temp2 ds 1 ; Temporary counter for delay. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 start mov !rb,#0 ; Output to show sequence on LEDs. mov lowB,#13 ; Arbitrary starting values mov hiB,#99 ; for the shift register. :loop call Random mov rb,lowB ; Display 8-bit random numbers call delay ; on LEDs connected to RB. jmp :loop ; Endless loop ; Random number generator. Random mov w,hiB ; First, ensure that hiB and lowB aren't OR w,lowB ; all zeros. If they are, NOT hiB to FFh. snz ; Otherwise, leave hiB and lowB as is. NOT hiB mov w,#80h ; We want to XOR hiB.7, hiB.6, hiB.4 snb hiB.6 ; and lowB.3 together in W. Rather than XOR hiB,w ; try to line up these bits, we just snb hiB.4 ; check to see whether a bit is a 1. If it XOR hiB,w ; is, XOR 80h into hiB. If it isn't, snb lowB.3 ; do nothing. When we're done, the XOR hiB,w ; XOR of the 4 bits will be in hiB.7. mov w,<
Please see the program listing under EEPROM.
Setting up a particular pin as an input or output requires writing a 1 or 0 to the corresponding bit of that port's TRIS register. However, the TRIS registers are write-only, so your program must keep copies of their contents in normal RAM to perform operations like Reverse. Here is a program fragment that shows how this works:
mov TRISB_copy,#11110000b ; Make a copy of TRISB. mov !rb,TRISB_copy ; Move copy into TRISB. ; >>TRISB now contains 11110000b. ... ; Other instructions. XOR TRISB_copy,#10000000b ; Reverse bit 7 of copy. mov !rb, TRISB_copy ; Move copy into TRISB. ; >>TRISB now contains 01110000b.Instead of moving a value directly into RB's TRIS register, the program moves it into a file register named TRISB_copy. Then it puts the value into TRIS. Later, when the equivalent of the Reverse instruction is required, the program reverses (inverts) the corresponding bit of the copy, and writes the copy to TRIS. This must be done consistently throughout a program in order to work properly. If at some point the copy no longer corresponds to the contents of TRIS, the method breaks down. That's why the programs in this book avoid TRIS manipulations whenever possible; they depend too heavily on the code in the rest of the program. The result could be bugs in the operation of these routines. However, don't let this stop you from incorporating powerful TRIS-copy techniques into your programs.
Serin lets a PIC application receive data from another PIC or any computer equipped with an RS-232 serial port.
Before we go into the details of using Serin, let's define a term: Polarity is the sense of the serial input. Normally, a computer that sends data via RS-232 will output it to an inverting line driver. The computer will output +5 volts for 1 and 0 volts for 0. The line driver will invert this and increase the voltage swing so that +10 volts is 0 and -10 volts is 1.
When we say "true" polarity, we mean the computer output (+5V = 1). "Inverted" polarity is like the output of the line driver, but without the wide voltage swing (+5V = 0). When you want to connect the PC's serial output directly to a PIC without a line receiver, use inverted polarity and a series resistor of approximately 22k. Static-protection diodes inside the PIC will clip the input voltage to +5V and ground. The resistor will limit the current from the RS-232 line into the PIC to a safe level.
To use Serin, make the desired pin an input and set up the filter flag. With filtering on, Serin will ignore bytes that fall outside the range of ASCII values defined by filt_lo and filt_hi. In the demo program, these are set to accept only the characters representing numbers 0 through 9. You could set them to A and Z or any other contiguous range of ASCII character values. If you want numeric text converted to binary values--as shown in the next entry, Serin (convert # data)--turn filtering on, and leave t he range set for numbers. Specify the baud rate by moving one of the constants B300 through B2400 into the variable baud. Specify the sense by setting or clearing the polarity bit (0 = true, 1 = inverted). Put the number of characters you wish to receive into bytes.
Put the pin number into pin and the port number into port. Call Serin. The routine will automatically receive the specified number of bytes into a buffer at the end of memory. When Serin returns, the data will be stored starting at buffer-1 and the count (number of bytes received) will be in the location buffer.
The reason Serin records the number of bytes actually received is that it can differ from the value you requested with bytes. When the filter is on, Serin will reject bytes data outside the filter range. When it receives the first byte that meets the filter requirements, it sets a flag called filt_1st. Once this flag is set, Serin will continue to accept bytes until it either receives the number specified by bytes, or a character outside the filter range arrives. This allows Serin to receive variable-length numbers and reject text.
To receive a 16-bit number, you would turn on the filter and put 5 in bytes. Serin would receive from 1 to 5 characters representing numbers from 0 to 65535. Input values must not contain commas; 65,535 would be received as "65".
When the filter is off, the buffer will contain exactly the number of bytes you specified. Serin can receive serial data through any pin of any I/O port, and can change port and pin assignments while the program is running. In many applications, it's enough to assign one pin for serial input and leave it at that. The disk that accompanies this book contains stripped versions of Serin for such applications. The advantage is that these use much less program memory than the routine listed here. Check the files SERIN_2.SRC and SERIN_3.SRC on the accompanying disk.
Serin, like the other routines that accept pin and port arguments, requires the short table Pinz. Remember that tables and subroutines must be located in the first 256 words of a 512-word program memory page.

; SERIN port, pin, baud, polarity, bytes, filter ; Receives data serially at the specified rate and polarity via the specified ; port pin. Stores the data as a counted string starting at buffer-1 (buffer is ; the number of bytes in the string). An optional filter causes the routine to ; ignore data outside a range of ASCII character values set by filt_lo and ; filt_hi. buffer = 31 ; String storage. B2400 = 30 ; Bit delay for 2400 baud. B1200 = 62 ; Bit delay for 1200 baud. B600 = 126 ; Bit delay for 600 baud. B300 = 255 ; Bit delay for 300 baud. filt_lo = '0' ; Set filter for ASCII filt_hi = '9' ; chars representing 0-9. org 8 baud ds 1 ; Baud rate. pin ds 1 ; Pin number (0-7). port ds 1 ; Port number (0-2). bytes ds 1 ; Number of bytes to receive. temp1 ds 1 ; Temporary counter for Serin. temp2 ds 1 ; Temporary counter for Serin. temp3 ds 1 ; Temporary counter for delay. ser_fl ds 1 ; Flags for serin switches. polarity = ser_fl.0 ; Polarity: 0=true, 1=inverted. filter = ser_fl.1 ; Range filter: 0=off, 1=on. filt_1st = ser_fl.2 ; 1st filter byte passed?: 0=no, 1=yes. aux = ser_fl.3 ; Temporary flag for filter comparisons. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 ; Table to convert pin number (0-7) into bit mask (00000001b to 10000000b). Pinz jmp pc+w retw 1,2,4,8,16,32,64,128 ; Subroutine used by Serin to get a bit and delay for a number of loops ; set by temp3. Jumping into get_bit:loop provides the delay, but ignores the ; input bit. get_bit mov fsr,port ; Point to port. mov w,indirect ; IF Polarity = 0 THEN w = Port snb Polarity ; ELSE w = NOT Port mov w,/indirect AND w,pin snz ; IF w AND pin THEN carry = 1 clc ; ELSE carry = 0. sz stc mov fsr,temp1 ; Point to buffer location. rr indirect ; Rotate carry into msb of data byte. :loop jmp $+1 ; Two-cycle nops. jmp $+1 jmp $+1 jmp $+1 jmp $+1 djnz temp3,:loop ret ; Main program start. Receives a single byte of data, filtered for numeric ; characters, and displays the data bits on LEDs connected to port RB. start mov !ra, #15 ; All inputs. mov !rb,#0 ; All outputs for LEDs. clr rc ; Clear the LEDs. clr ser_fl ; Clear serial flags. mov bytes,#1 ; Receive 1 byte clrb filter ; not filtered setb polarity ; inverted polarity mov baud,#B2400 ; at 2400 baud mov pin,#2 ; via pin 2 mov port,#0 ; of port ra. call Serin ; Receive the data. mov rb,buffer-1 ; Move byte to LEDs on RB. jmp start ; Endless loop ; Body of the Serin routine. Expects the baud constant in baud, number of ; bytes to receive in bytes, the I/O port (0-2 for RA through RC) in port, ; the pin number (0-7) in pin, and the flag settings in ser_fl Serin clr buffer ; Clear buffer counter. clrb filt_1st ADD port,#RA ; Add offset for port RA. mov w,pin call Pinz ; Get bit mask from the table. mov pin,w ; Put the mask into pin. mov temp1,#buffer-1 ; Pointer for first data address. :poll mov fsr,port mov w,indirect ; IF Polarity = 0 THEN w = Port snb Polarity ; ELSE w = NOT Port mov w,/indirect AND w,pin ; IF pin = 0 THEN receive data jnz :poll ; ELSE poll clc mov w,>>baud ; LET temp3 = baud/2 mov temp3,w ; Set up 1/2 bit time delay. call get_bit:loop ; Jump into delay of get_bit. mov temp3,baud ; Set up full bit time delay. call get_bit:loop ; Jump into delay of get_bit. mov temp2,#8 ; Eight data bits. :rcv mov temp3,baud ; Set up bit delay. call get_bit ; Get the next data bit. djnz temp2,:rcv ; Eight bits received? :done mov temp3,baud ; Set up bit delay. call get_bit:loop ; Wait for stop bit. jnb filter,:skip ; IF filter=0 (off) THEN :skip clrb aux ; LET aux = 0. csae indirect,#filt_lo ; IF byte < filt_lo THEN aux=1 setb aux csbe indirect,#filt_hi ; IF byte > filt_hi THEN aux=1 setb aux jnb aux,:skip ; If aux = 0 (byte is within snb filt_1st ; filter range) :skip. If byte is ret ; out of range, but valid bytes have jmp :poll ; been received, return. Else poll. :skip setb filt_1st dec temp1 ; Decrement buffer pointer. inc buffer ; Increment buffer counter. djnz bytes,:poll ; More bytes to receive: poll. ret
PBASIC's Serin can automatically convert incoming text to numbers. This means that a user can type a number like "5280" from the keyboard of a PC or terminal, and Serin will interpret the received string as the 16-bit number 5280 (14A0h).
Using the assembly version of Serin, you can do the same thing. Set up Serin to receive five bytes (for the five digits of a 16-bit number) and set the filter bit. Make sure that the filter range, determined by equates at the beginning of the Serin listing, is set up for ASCII '0' through '9'.
Serin will ignore data other than numbers. Once it begins receiving numeric text, it will store up to five digits in its string buffer. Once five digits are collected or the numeric string is ended by a non-numeric character, Serin will return.
An example will help clarify this. Suppose Serin is called with the filter on and a request for five bytes. The following stream of data arrives:
1st 2nd 3rd 4th 5th 6th 7th 8th 9th Q ( 5 2 0 ) ( 9 1Serin rejects the first and second bytes. It recognizes the third through fifth bytes as numbers and stores them in the string. It now has storage remaining for two more numeric bytes. However, the sixth byte is not a number, so Serin stops listening and returns with the three-byte string "520" in the buffer. If it didn't stop, it could skip the parentheses and combine "520" with "91" to produce "52091". That is clearly not the right answer.
Serin will also reject mathematical punctuation, like the minus sign, comma, and decimal point. People or programs sending data for use by Serin must not use these marks in their entries.
Once Serin has stored the data, a call to ASC_BIN converts it to a 16-bit number. If there is no data in the string (the first entry containing the number of bytes is 0), the routine returns with 1 in w to signal the error. Otherwise, it returns the binary equivalent of the text string in the 16-bit variable n1 and a 0 in w. ASC_BIN decrements the string counter to 0, so if the string is to be reused, your program should copy this value and rewrite it after ASC_BIN is done.

ASC_BIN numeric string ; This program converts a counted string of ASCII characters ; received by Serin into a 16-bit value in the variable n1. buffer = 31 ; String buffer at end of memory ASC_0 = '0' ; ASCII chars for nos. are 30h thru 39h. org 8 dec_no ds 1 ; Decade (1,10,100...) to work on. n1H ds 1 ; MSB of converted number. n1L ds 1 ; LSB of converted number. n2H ds 1 ; MSB of 16-bit temp variable. n2L ds 1 ; LSB of 16-bit temp variable. temp ds 1 ; Temporary storage ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 decade jmp pc+w retw 39,16,3,232,0,100,0,10,0,1 start mov !rb,#0 ; Set ports to output. mov !rc,#0 mov buffer,#5 ; String count. mov buffer-1,#36h ; Numeric text: "65535." mov buffer-2,#35h ; To view the results of the mov buffer-3,#35h ; conversion, run this program mov buffer-4,#33h ; on the PSIM simulator, or merge mov buffer-5,#35h ; it with Serin. call ASC_BIN mov rb,n1L ; Show result on LEDs. mov rc,n1H jmp $ ; Endless loop. ; ASC_BIN gets the value of each digit (0-9) of a numeric string. Depending ; on the number of digits in the string, it looks up the multiplier (10000, 1000,100,10, or 1) and adds that multiplier to n1 digit number of times. ASC_BIN clr n1H ; n1 = 0 clr n1L mov fsr,#buffer-1 ; If the string is empty, test buffer ; return 0 in 16-bit answer jnz :loop ; and error code (1) in retw 1 ; w. Else, do the conversion. :loop mov w,#ASC_0 ; w = 30h (ASCII start point). subwf indirect,0 ; w = character_value-30h. jz :next mov temp,w ; temp = w mov dec_no,#5 sub dec_no,buffer ; dec_no = 5-string_length clc rl dec_no ; dec_no = dec_no*2 mov w,dec_no call decade ; Look up MSB of multiplier. mov n2H,w ; Store it in MSB of n2. mov w,++dec_no ; Look up the LSB of multiplier. call decade mov n2L,w ; Store it in LSB of n2. :add_it call Add16 ; n1 = n1 + n2. djnz temp,:add_it ; Repeat 1 to 9 times for each digit. :next dec fsr ; Get the next byte of the string. dec buffer ; Adjust string counter. jnz :loop ; Any bytes left? Yes: loop. ret ; No: return. Add16 ADD n1L,n2L ; Add the LSBs. If carry, snc ; increment MSB of sum. inc n1H ADD n1H,n2H ; Add the MSBs. ret
Another feature of PBASIC's Serin is the ability to compare incoming serial data to a table of qualifiers. Serin halts program execution until it receives data that exactly matches the qualifiers. This makes it possible to recognize key words in a text stream, or to protect access to a sensitive program.
You can add this capability to Serin using the routine Qualify and a few additional instructions. The listing shows how this works. Rather than present the entire Serin listing again, only the Qualify code appears here. To simulate receiving data with Serin, execute up to the ret at the end of the dummy Serin, then enter a byte at address 01Eh (buffer-1).
Qualifiers must match the case of the password. "SESAME" is not the same as "Sesame" as far as Qualify is concerned. Also, make sure to turn off the Serin filter flag if your password contains characters outside the range of filt_lo to filt_hi.
Serin and Qualify will wait forever to receive the password sequence. You may add code to count the number of bytes received and give up if the proper sequence is not submitted within a preset number of tries.

; QUALIFY table, string ; Reads a one-character Serin string and looks up the character in ; a table of "qualifiers." If the character matches the next qualifier, ; the routine increments a counter and checks to determine whether all ; qualifiers have been matched. If so, it returns a 1 in w. If not, ; it returns a 0 in w after resetting the counter to the first item in ; the qualifier list. org 8 qual ds 1 ; Pointer to qualifiers. buffer = 31 ; Serin string address. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 P_word jmp pc+w retw 6 ; No. of elements in password. retw 'Sesame' ; The password. ; Below is a dummy Serin routine intended for use on the PSIM simulator. ; Wait until PSIM reaches the "ret" below, then change the data at buffer-1 ; (hex address 1E). You can step through Qualify to see how it works. ; The password "Sesame" is 53h, 65h, 73h, 61h, 6Dh, 65h. Serin mov buffer-1,#'A' ; Dummy Serin for demo. ret ; Enter character data here. start mov !ra,#4 ; Input for serial data. mov !rb, #0 ; All outputs for LEDs clr rb ; LEDs off to begin. clr qual ; Clear qual to start. :loop call Serin ; Receive one byte. call Qualify ; Check it against password. test w ; IF w=1 THEN LEDs_on snz ; ELSE :loop jmp :loop XOR rb,#255 ; Password match: toggle LEDs. jmp start ; Do it again. ; Upon entry, a one-byte Serin string should be stored at buffer-1. ; Qualify will check it against the next entry in the P_word table, and ; if there's a match, will increment the qualifier counter qual. If there's ; no match, it will clear qual, and check the character against the first ; entry in P_word. This second step prevents Qualify from failing to ; recognize passwords after a false start, like "SeSesame." Qualify mov w,++qual ; Look up next item in P_word. call P_word subwf buffer-1,0 ; Compare it to received byte by jnz :no_match ; subtraction. If result <> 0, no match. inc qual ; Match: increment qual pointer and clr w ; get the 0th item in the table (number call P_word ; of chars in the password). subwf qual,0 ; IF qual = no._of_P_word_chars THEN snz ; return with 1 in w. retw 1 ; ELSE return with 0 in w. ret :no_match test qual ; IF qual = 0, return with 0 in w. snz ret clr qual ; ELSE qual = 0: GOTO Qualify (check jmp Qualify ; char against 0th password entry).
Serout lets a PIC application transmit data to another PIC or any computer equipped with an RS-232 serial port.
If you have not already done so, please read the discussion in paragraph two of the first Serin entry (page 74) on the meaning of "polarity" as it applies to these serial routines.
When you want to connect the PIC's serial output directly to a computer without a line driver, use inverted polarity. Even though the voltage swing isn't legal RS-232, this will generally work, provided cables are kept short.
To use Serout, put the desired pin into the stop-bit state (0 for inverted output, 1 for true) and make the pin an output. This will prevent sending a false start bit to the receiver.
Next, create a string of the bytes you want to transmit at the location labeled buffer. Store the number of bytes at buffer, the first byte at buffer-1, the second byte at buffer-2, and so on.
Specify the baud rate by moving one of the constants B300 through B2400 into the variable baud. Specify the sense by setting or clearing the polarity bit (0 = true, 1 = inverted). Put the pin number into pin and the port number into port. Call Serout. The routine will automatically send each byte of the string.
When Serout returns, the data starting at buffer-1 will be intact, but the count (at buffer) will have been cleared to 0.
Note that Serout permits your program to make a serious mistake; If the count at buffer is 0 when Serout is called, the routine will attempt to send 256 bytes of data. It will alter ports and control registers in the lower seven or eight bytes of RAM. Make sure that buffer contains the correct count before calling Serout.
If your application requires sending a number out the serial port as a text string, see the next entry, Serout (format # data). It converts a 16-bit value into a Serout string with an optional fixed decimal point.
Note that this version of Serout does not support the open-drain/open-source capabilities of the PBASIC version. Those require manipulation of the TRIS register, which we've avoided in this series of subroutines.
Serout can transmit serial data through any pin of any I/O port, and can change port and pin assignments while the program is running. In many applications, it's enough to assign one pin for serial output and leave it at that. The disk that accompanies this book contains stripped versions of Serout for such applications. The advantage is that they use less program memory than the routine listed here. Check the files SEROUT_2.SRC and SEROUT_3.SRC on the accompanying disk.
Serout, like the other routines that accept pin and port arguments, requires the short table Pinz. Remember that tables and subroutines must be located in the first 256 words of a 512-word program memory page.

; SEROUT port (in w), pin, baud, polarity, data ; Transmits data serially at the specified rate and polarity out the specified port ; pin. Data bytes to be transmitted must be arranged in memory starting at the ; address "buffer-1" and working downward (e.g., 30,29,28...). The number of ; bytes to transmit must be stored at the address labeled "buffer." A companion ; routine, BIN_ASC, converts 16-bit numbers to this form, optionally adding a ; decimal point. See the listing for Serout ...(format # data). ; Serout uses the common data format of no parity, 8 data ; bits, 1 stop bit (N81). buffer = 31 ; String storage at end of memory. B2400 = 30 ; Bit delay for 2400 baud at 4MHz. B1200 = 62 ; Bit delay for 1200 baud at 4MHz. B600 = 126 ; Bit delay for 600 baud at 4MHz. B300 = 254 ; Bit delay for 300 baud at 4MHz. org 8 baud ds 1 ; Baud rate. pin ds 1 ; Pin number (0-7). port ds 1 ; Port number (0-2). temp1 ds 1 ; Temporary counter for Serout. temp2 ds 1 ; Temporary counter for Serout. temp3 ds 1 ; Temporary counter for delay. polarity = PA2 ; Polarity: 0=true, 1=inverted. ; Note that the polarity flag is the unused page-address bit of the PIC 16C5x ; series. If you port this program to another type of PIC, or another routine uses ; this bit, you may have to reassign it. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 ; Table to convert pin number (0-7) into bit mask (00000001b to 10000000b). Pinz jmp pc+w retw 1,2,4,8,16,32,64,128 ; Subroutine used by Serout to send a bit with the specified polarity (true or ; inverted) and timing (300, 600, 1200, or 2400 bps). emitBit mov fsr,port ; Point to the output port. mov temp3,baud ; Load baud rate into temp counter. mov w,status ; Copy carry bit (data bit) into w. snb polarity ; If polarity = 1 then invert w.0. XOR w,#1 AND w,#1 ; Copy w.0 back to carry bit. clc ; (This roundabout method of inverting snz ; carry is needed since "XOR status,w" stc ; doesn't work properly.) ; This loop deserves some explanation. The first time through, it sets or clears ; the serial output bit. Thereafter, it's just a delay loop. :loop mov w,pin ; Get the pin mask. sc ; Data in carry bit. OR indirect,w ; If carry = 0 then set bit. mov w,/pin snc AND indirect,w ; Else if carry = 1 then clear bit. jmp $+1 ; Two-cycle nops for time delay. jmp $+1 djnz temp3,:loop ; Number of trips through loop ret ; set by baud rate in temp3. ; Start of demo program. Upon power up or reset, the PIC transmits the string ; "OK" via pin 2 of port ra at 2400 baud, inverted polarity start clr ra ; High is a start bit, so hold ra low. mov !ra, #0 ; All outputs. mov buffer,#2 ; Put test string "OK" mov buffer-1,#'O' ; into the buffer, starting with the mov buffer-2,#'K' ; number of characters. setb polarity ; Inverted for direct connection. mov baud,#B2400 ; 2400 baud. mov pin,#2 ; Pin 2. mov port,#0 ; of port ra. call Serout ; Send the data. jmp $ ; Endless loop ; Upon entry, the desired pin must already be set up as an output. ; The w register contains a number representing the output port (0-2) for ; RA through RC. Variable "pin" contains the pin number (0-7). The ; variable baud must contain a number representing the baud rate. ; The polarity flag must be set (1) for true, or cleared (0) for inverted ; output. A data string consisting of the number of bytes, followed by the ; bytes arranged downward in memory, must be at the location "buffer." ; Upon return, the data in the buffer will be unchanged, but the 0th entry ; (representing the string length) will have been decremented to 0. Serout add port,#RA ; Add offset for port RA. mov w,pin call Pinz ; Get bit mask from the table. mov pin,w ; Put the mask into pin. mov temp1,#buffer-1 ; Point to first data address. :xmit clc ; Start bit into carry call emitBit ; Send start bit. mov temp2,#8 ; Send 8 data bits. :bits mov fsr,temp1 ; Point to data byte. rr indirect ; Rotate bit 0 into carry. call emitBit ; Send the data bit. djnz temp2,:bits ; All 8 bits sent? No: send more bits. mov fsr,temp1 ; Point to the data byte. rr indirect ; Rotate it back into original position. sb polarity ; If it was inverted, invert it again to NOT indirect ; restore original. stc ; Move stop bit into carry call emitBit ; Send the stop bit. dec temp1 ; Point to next data byte. djnz buffer,:xmit ; All bytes sent? No: send more bytes. ret ; Yes: return.
The PBASIC version of Serout has a tremendously convenient feature; the ability to treat data for transmission as either bytes or numbers. The value 65, if sent as a byte, would appear as the letter "A" on the receiving end. If sent as a number, "65" shows up at the receiver. This routine, called BIN_ASC for binary-to-ASCII, converts a pair of bytes (interpreted as a 16-bit number) into text. For example, if the bytes both contain 0FFh, BIN_ASC creates a text string containing "65535" ready for transmission by Serout.
The routine suppresses leading zeros, just as the PBASIC version does. So the number 17 is output as "17" and not "00017".
BIN_ASC goes one step beyond the conversion built into the PBASIC's Serout by allowing you to specify a fixed decimal point in positions 0 through 4 of the text string. Here are examples of the output:
Fixed Point at: Value = 1 Value = 65535 0 .00001 .65535 1 .0001 6.5535 2 .001 65.535 3 .01 655.35 4 .1 6553.5 5 1 65535By setting the fixed point to a value of 5 or greater, you can eliminate it entirely. BIN_ASC can also prepare data for other types of ASCII output devices, such as LCDs or parallel printers. Just write the driver to expect a Serout-style counted string, as described in the previous Serout entry.
The routine uses a short table called decade. Remember that tables and subroutines must be located in the first 256 words of a 512-word program memory page.
For a live demonstration, connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble SEROUT_#.SRC from the accompanying disk and program the PIC or downloader with the assembled program. Make sure to use a 4-MHz clock on the PIC or downloader. Load a terminal program on the PC connected to the PIC and set it for 2400 baud, no parity, 8 data bits, 1 stop bit (N81). When you apply power to the PIC or reset the downloader, the string "655.35" will appear on the PC s creen.

; BIN_ASC 16-bit value ; This routine converts a 16-bit number into a counted string of ; ASCII characters compatible with Serout. It suppresses ; leading zeros so that numbers like 17 are sent as "17" not "00017". buffer = 31 ; String buffer at end of memory. ASC_0 = '0' ; ASCII numbers: 30h thru 39h. ASC_dp = '.' ; ASCII char for decimal point (period). fix = 3 ; Position for fixed decimal point. org 8 dec_no ds 1 ; Decade (1,10,100...) to work on. tempH ds 1 ; 16-bit temporary variable used by tempL ds 1 ; the BIN_ASC routine. hiB ds 1 ; MSB of number to convert. lowB ds 1 ; LSB of number to convert. flags ds 1 zs = flags.0 ; Leading-zero suppression flag. device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 decade jmp pc+w retw 39,16,3,232,0,100,0,10,0,1 start mov hiB, #0FFh ; To see routine's output, either mov lowB, #0FFh ; use the PSIM PIC simulator, or call BIN_ASC ; merge with Serout. jmp $ ; Endless loop ; This routine accepts a 16-bit number in hiB, lowB and converts it into ; a counted ASCII text string located at the address "buffer." The buffer ; grows downward in memory. The first (0th) item in buffer is the number ; of characters in the string. BIN_ASC clr buffer ; Clear char count (item 0 in buf). clr flags ; Clear zs flag. clr dec_no ; Clear decade no. mov fsr,#buffer-1 ; Reserve 0th byte of buffer for count. :loop mov w,dec_no ; Get 1st hex digit of decade no. call decade ; from the lookup table. mov tempH,w inc dec_no ; Get 2nd digit of decade no. mov w,dec_no ; from the table. call decade mov tempL,w call d_point ; Insert decimal point. call sub_it ; Divide hiB,lowB by tempH,tempL mov w,indirect ; returning answer in indirect. jb zs,:no_zs ; If zs = 0 AND digit = 0 then jz :no_zed ; digit is a leading zero to be :no_zs mov indirect,w ; ignored. Otherwise, it's either ADD indirect,#ASC_0 ; an included zero (as in 7501) inc buffer ; or a non-zero digit. dec fsr ; Point to next memory location. setb zs ; First non-zero digit sets zs bit. :no_zed inc dec_no ; Next decade. cse dec_no,#8 ; If dec_no = 8, we're down to ones. jmp :loop ; Otherwise, do next decade. inc dec_no ; Update dec_no call d_point ; and call d_point. mov indirect,lowB ; Whatever's left belongs in ones. ADD indirect,#ASC_0 ; Add offset for conversion to ASCII. inc buffer ; Add 1 to character count. ret ; This routine performs division by iterated subtraction. It is efficient ; in this application because the dividend and divisor keep getting smaller ; as BIN_ASC runs, so the quotient is never larger than nine. A general- ; purpose division routine would be slower (and longer). sub_it clr indirect ; Clear to track no. of subtractions. :loop sub lowB,tempL ; Subtract LSB. jc :skip ; If no borrow, continue w/MSB. sub hiB,#1 ; Otherwise borrow from MSB. jc :skip ; If borrow causes a carry, then inc hiB ; add numbers back and return. add lowB,tempL ret :skip sub hiB,tempH ; Subtract MSB. jc :skip2 ; If no borrow, subtract again. ADD lowB,tempL ; Otherwise, undo the subtraction snc ; by adding entire 16-bit no. inc hiB ; back together and return. ADD hiB,tempH ret :skip2 inc indirect ; No borrow, so do it again. jmp :loop ; This routine adds a decimal point in the location set by "fix" in the ; equates at the beginning of the program. The location of the decimal point ; is in front of the "fix"ed digit, numbered starting with 0. If you fix the ; point at 0, the first (0th) character in the string produced by BIN_ASC ; will be a decimal point. If you don't want a decimal point, either move ; it out of range (fix = 6), or delete this routine and the "call d_point" ; in the body of BIN_ASC above. d_point cse dec_no,#fix*2+1 ret setb zs mov indirect,#ASC_dp inc buffer dec fsr ret
As the name implies, Sleep is the long version of Nap. Certain applications, like data logging, require the PIC's attention at infrequent intervals; once a minute, once an hour, even once a day. Sleep makes it possible to reduce current draw to 3 µA (other than any loads driven by I/O pins) between these tasks. When it's time to perform another task, the PIC wakes up automatically.
To use Sleep, put the duration desired (1 to 65,535 units of 2.3 seconds each) into the hiB and lowB, and call the subroutine. Sleep changes hiB and lowB to their two's complements, sets the timer prescaler to divide by 128, assigns it to the watchdog timer, and puts the PIC to sleep.
Every 2.3 seconds thereafter, the PIC wakes up briefly. It looks at the timeout (to) and power-down (pd) bits of the status register. If the reset was caused by a watchdog timeout awakening the PIC out of sleep (to = 0, pd = 0), the program increments lowB and hiB. If the 16-bit register overflows, it's time to wake up. If not, the PIC goes back to sleep. As you can see from the example listing, Sleep requires more than just the subroutine to work. The device directive must include wdt_on to enable the watchdog timer. The code at the beginning of the program must examine the to and pd bits and decide what to do. And, although it's not shown in the example, the program must clear the watchdog timer (clr wdt) frequently enough to prevent it from interrupting the running program. How frequently depends on the value in the prescaler and whether or not the rest o f the program uses the prescaler with the RTCC.
The safest answer is to clear the watchdog at least every 9 ms. Then, no matter how the option register is set up or what the actual speed of the watchdog's oscillator is, the wdt will never overflow.
Another issue concerns what to do about the wdt-invoked reset and its effect on the I/O pins. During a reset, all pins are set to input. If the PIC is driving a load during its nap, there will be a brief glitch as the TRIS registers are reset. It is best not to drive loads directly during naps, but to rely on pullup or pulldown resistors to do the job instead. Finally, remember that all computed jumps and called subroutines must be in the first 256 words of a program-memory page. In this case, that would include both Nap and the decision-making code in start.
Manufacturing variations in the PIC, and changes in supply voltage and operating temperature affect the period of the watchdog timer's oscillat or. At extremes of temperature and voltage, it can range from 9 ms to 40 ms. This makes it a pretty unreliable alarm clock. It seems likely that the BASIC Stamp performs a self-calibration to reduce this error. Since the Stamp always has math functions at its disposal, this makes sense. In the interest of keeping this code short and clear, I've omitted a calibration step.

; SLEEP period ; Puts the PIC to sleep for 1 to 65535 units of 2.3 seconds. ; The 2.3-second value is approximate. Wide variations in operating temperature ; or supply voltage can double or halve this figure. See the discussion in the ; Nap program listing. org 8 hiB ds 1 lowB ds 1 ; Device data and reset vector: Note that the watchdog timer is ON. device pic16c55,xt_osc,wdt_on,protect_off reset start org 0 Sleep NOT hiB ; Take twos complement NEG lowB ; of the 16-bit counter snz ; If zero, lowB overflowed, inc hiB ; so carry into hiB. mov w,#15 ; Assign prescaler (div 128) mov option,w ; to wdt, then write to option. sleep ; Go to sleep. ; SLEEP (cont) start mov !rb,#0 ; Make RB all outputs for LEDs. clr w ; At the beginning of the program, snb pd ; we set up a Branch-type routine OR w,#1 ; to take action based on the snb to ; power-down (pd) and timeout (to) OR w,#2 ; bits. jmp pc+w ; | to | pd| jmp count_dn ; | 0 | 0 | wdt wakeup: count down. jmp wdt_fail ; | 0 | 1 | wdt timeout: handle it. jmp mclr_pin ; | 1 | 0 | mclr wakeup: handle it. jmp main ; | 1 | 1 | power 1st on: goto main. ; This code executes when power is first applied to the PIC (after start). ; It consists of a loop in which the PIC sleeps for 2 hours, then inverts port RB. main mov hiB,#0Ch ; Load 3125 (0C35 hex) mov lowB,#35h ; into 16-bit counter. call Sleep ; Hibernate for 3125 x 2.304 secs. XOR rb,#255 ; Wake up after 2 hours, invert RB. jmp main ; Do it again. ; When the PIC wakes up from a wdt timeout, it executes this code, which ; increments the 16-bit value in lowB and hiB, and determines whether it's ; time to wake up. If it's not, the PIC goes back to sleep. If it is, the program ; returns to resume execution at the instruction following call Sleep. count_dn inc lowB ; lowB = lowB+1. snz ; Overflow in lowB? incsz hiB ; Yes: hiB=hiB+1, overflow?. sleep ; No overflow: back to sleep. ret ; Overflow. Return to caller ; In an actual application, this code would contain instructions for dealing ; with a reset pulse waking the PIC out of sleep. Here, we have an empty ; routine that just returns to the main loop. mclr_pin ret ; In an actual application, this code would contain instructions for dealing ; with a watchdog-timer timeout during program execution. Here, we have an ; empty routine that just returns to the main loop. wdt_fail ret
The Sound routine generates squarewave tones with frequencies of 280 Hz to 71 kHz and durations of 3.6 ms to 0.92 seconds at 4 MHz. It differs from its PBASIC counterpart in that all frequency values from 1 to 255 produce squarewave tones. PBASIC's Sound instruction generates tones when the frequency is in the range of 1 to 127, and hissing white-noise effects from 128 to 255. Both PBASIC and assembly versions of Sound produce a silent pause with a frequency value of 0.
To use the routine, store a frequency value from 0 to 255 in freq, and a duration from 0 to 255 in duratn. Put the output pin number (0 to 7) into pin and the port number (0 to 2 for RA through RC) into the w register, and call Sound.
The following formula determines the frequency output by Sound (assuming a PIC clock speed of 4 MHz):

PBASIC's Sound command has the same characteristic, but starts slightly below 100 Hz. It has a period resolution of approximately 83µs. If you want to more closely emulate PBASIC, just pad the :loop of this Sound routine to take a total of 83µs.
The PBASIC command permits you to specify a list of frequencies and durations to be played. The demonstration program shows how to do this in assembly by using a pair of lookup tables. If you need white-noise effects in your program, use the Random routine. Call Random from within a tight program loop, and copy one bit of its shift register to the speaker output. The random bit stream will produce a hissing sound.
One hint on using Sound: always assign the port number to w immediately before calling the routine. If you don't, some other instruction that uses w may change its contents, causing an error.

; SOUND port, pin, frequency, duration ; Generates squarewave tones (notes) of the specified frequency and ; duration. This demonstration program shows how to use lookup tables to ; play tunes or effects from data stored in program memory. org 8 pin ds 1 ; Pin number to use as output (0-7). freq ds 1 ; Frequency of note. duratn ds 1 ; Duration of note. f_copy ds 1 ; Copy of freq variable. temp ds 1 ; Variable to stretch duratn to 16 bits. index ds 1 ; Index to tables of notes, lengths. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 ; Table to convert pin number (0-7) into bit mask (00000001b to 10000000b). Pinz jmp pc+w retw 1,2,4,8,16,32,64,128 ; Table of notes for the demonstration tune "Charge." Notes jmp pc+w retw 166,189,204,212,204,212 ; Table of note durations for the demonstration tune "Charge." Lengths jmp pc+w retw 52,52,52,82,45,190 start mov !ra, #0 ; Output for speaker. clr temp ; Clear LSB of duratn counter. clr index ; Clear the note counter. :loop mov w,index ; Look up note in table. call Notes mov freq,w ; Store note in freq. mov w,index ; Look up length in table. call Lengths mov duratn,w ; Store in duratn. mov pin,#3 ; Select pin 3 of port mov w,#0 ; RA for speaker output call Sound ; Play the sound. inc index ; Advance to next sound in tables. cjbe index,#5,:loop ; IF index <= 5, play note jmp $ ; ELSE end. ; Upon entry, the desired speaker pin must already be set up as an output. ; The w register contains a number representing the output port (0-2) for ; RA through RC. Variable "pin" contains the pin number (0-7). The variables ; freq and duratn are the frequency and duration of the note. ; Upon exit, duratn will be cleared to 0, but freq will be intact. If you require the ; value of duratn for some other purpose, copy it before calling Sound. The ; variable pin and the file-select register (fsr) will also have be altered. Sound NEG duratn ; Convert to two's complement. mov fsr,w ; Point to the port number. add fsr,#RA ; Add offset for port RA. mov w,pin ; Now look up pin number in call Pinz ; table and get bit mask. mov pin,w ; Put the mask into pin. mov f_copy,freq ; Use copy so freq value is saved. snz ; If freq is 0, clear pin to clr pin ; produce a silent delay. :loop mov w,pin ; f_copy=f_copy+1: IF f_copy<=255 incsz f_copy ; THEN w=pin ELSE w=0 clr w ; (makes a silent delay if freq = 0) XOR indirect,w ; indirect=indirect XOR w mov w,f_copy ; If f_copy has rolled over to 0, snz ; reload it with the value of freq. mov w,freq mov f_copy,w inc temp ; Increment LSB of duratn counter. snz ; Overflow? Yes: Increment duratn and incsz duratn ; if it overflows, return. Else loop. jmp :loop ret
In assembly language, inverting the state of a pin (or any bit, for that matter) is a matter of XORing the register with a value containing a 1 in the corresponding position. For example, to invert bit 2 of port RA, you would use the instructions XOR ra, #00000100b.
PBASIC's Toggle also changes the pin's data direction to output by putting a 0 in the appropriate bit of the tristate (TRIS) register. (In assembly, a 0 in TRIS means makes a pin and output; a 1 makes it an input. This is the opposite of the BASIC Stamp.) The assembly-language example here changes only the output latch, not TRIS.
Of course, if you use a literal number to toggle a pin's state with XOR, you cannot change the pin to be toggled while the program is running. The literal number is coded as part of the machine-language instruction. The accompanying program listing is a port- and pin-independent Toggle routine. Write the pin number to pin, the port number to w, and call Toggle.

; TOGGLE port (in w), pin ; Toggles (inverts) the state of the specified port and pin. org 8 pin ds 1 ; Pin number to set (0-7). ; Variables for the demo program--not required by Toggle. p_index ds 1 ; Copy of pin number, port number. temp ds 1 ; Temporary variables for time delay temp2 ds 1 ; between outputs. ; Device data and reset vector device pic16c55,xt_osc,wdt_off,protect_off reset start org 0 ; Table to convert pin number (0-7) into bit mask (00000001b to 10000000b). Pinz jmp pc+w retw 1,2,4,8,16,32,64,128 ; The following demonstration code should show the value of being able to ; toggle a particular bit of a port based on a calculation performed while the ; program is running. As the variable p_index is incremented from 0 to 15, ; it is used to specify the pin and port to invert using Toggle. start mov !rb, #0 ; All outputs on rb. mov !rc, #0 ; All outputs on rc. mov rb,#10101010b ; Put a pattern of alternating lights mov rc,#10101010b ; onto rb and rc. clr p_index ; Clear p_index. :loop mov pin,#7 ; Copy three lsbs of p_index into AND pin,p_index ; variable pin. mov w,#1 ; IF p_index.3 = 0 snb p_index.3 ; THEN w = 1 mov w,#2 ; ELSE w = 2 call Toggle call delay ; Wait a while between toggles. inc p_index ; Next pin/port. sb p_index.4 ; IF p_index < 15 (rc.7) jmp :loop ; THEN :loop ELSE done done jmp $ ; Endless loop. ; To use the routine Toggle, put the pin number into the variable pin ; and the port number into w. Then call Toggle. Toggle mov fsr,w ; Point to the port number. ADD fsr,#RA ; Add offset for port RA. mov w,pin call Pinz ; Get bit mask from the table. XOR indirect,w ; Invert the selected bit ret ; Delay routine for demonstration. Not required by Toggle. delay djnz temp,delay ; Time delay to provide spacing djnz temp2,delay ; between highs. ret
Please see the program listing under EEPROM.
Keep this information handy as you write and debug programs. Some of the most difficult bugs to locate and eliminate are those caused by:
You can also use the chart to help you calculate the time required for your code to execute. Just add the number of program cycles for each instruction. This can get a little tricky within loops, so I frequently cheat and use the PSIM simulator to do my counting for me.
| Instruction | Operands | Words | Cycles | Affects |
| ADD | fr, #literal | 2 | 2 | C, DC, Z |
| fra, frb | 2 | 2 | C, DC, Z | |
| fr, W | 1 | 1 | C, DC, Z | |
| W, fr | 1 | 1 | C, DC, Z | |
| ADDB | fr, bit | 2 | 2 | Z |
| AND | fr, #literal | 2 | 2 | W, Z |
| fra, frb | 2 | 2 | W, Z | |
| fr, W | 1 | 1 | Z | |
| W, #literal | 1 | 1 | Z | |
| W, fr | 1 | 1 | Z | |
| CALL | addr8 | 1 | 2 | none |
| CJA | fr, #literal, addr9 | 4 | 4/5 | C, DC, Z |
| fr1, fr2, addr9 | 4 | 4/5 | C, DC, Z | |
| CJAE | fr, #literal, addr9 | 4 | 4/5 | C, DC, Z |
| fr1, fr2, addr9 | 4 | 4/5 | C, DC, Z | |
| CJB | fr, #literal, addr9 | 4 | 4/5 | C, DC, Z |
| fr1, fr2, addr9 | 4 | 4/5 | C, DC, Z | |
| CJBE | fr, #literal, addr9 | 4 | 4/5 | C, DC, Z |
| fr1, fr2, addr9 | 4 | 4/5 | C, DC, Z | |
| CJE | fr, #literal, addr9 | 4 | 4/5 | C, DC, Z |
| fr1, fr2, addr9 | 4 | 4/5 | C, DC, Z | |
| CJNE | fr, #literal, addr9 | 4 | 4/5 | C, DC, Z |
| fr1, fr2, addr9 | 4 | 4/5 | C, DC, Z | |
| CLC | none | 1 | 1 | C |
| CLR | fr | 1 | 1 | Z |
| CLR | W | 1 | 1 | Z |
| CLR | WDT | 1 | 1 | TO, PD |
| CLRB | bit | 1 | 1 | none |
| CLZ | none | 1 | 1 | Z |
| CSA | fr, #literal | 3 | 3/4 | C, DC, Z |
| fr1, fr2 | 3 | 3/4 | C, DC, Z | |
| CSAE | fr, #literal | 3 | 3/4 | C, DC, Z |
| fr1, fr2 | 3 | 3/4 | C, DC, Z | |
| CSB | fr, #literal | 3 | 3/4 | C, DC, Z |
| fr1, fr2 | 3 | 3/4 | C, DC, Z | |
| CSBE | fr, #literal | 3 | 3/4 | C, DC, Z |
| fr1, fr2 | 3 | 3/4 | C, DC, Z | |
| CSE | fr, #literal | 3 | 3/4 | C, DC, Z |
| fr1, fr2 | 3 | 3/4 | C, DC, Z | |
| CSNE | fr, #literal | 3 | 3/4 | C, DC, Z |
| fr1, fr2 | 3 | 3/4 | C, DC, Z | |
| DEC | fr | 1 | 1 | Z |
| DECSZ | fr | 1 | 1/2 | none |
| DJNZ | fr, addr9 | 2 | 2/3 | none |
| IJNZ | fr, addr9 | 2 | 2/3 | none |
| INC | fr | 1 | 1 | Z |
| INCSZ | fr | 1 | 1/2 | none |
| JB | bit, addr9 | 2 | 2/3 | none |
| JC | addr9 | 2 | 2/3 | none |
| JMP | addr9 | 1 | 2 | none |
| JMP | PC+W | 1 | 2 | C, DC, Z |
| JMP | W | 1 | 2 | none |
| JNB | bit, addr9 | 2 | 2/3 | none |
| JNC | addr9 | 2 | 2/3 | none |
| JNZ | addr9 | 2 | 2/3 | none |
| JZ | addr9 | 2 | 2/3 | none |
| LCALL | addr11 | 1-3 | 2-4 | none |
| LJMP | addr11 | 1-3 | 2-4 | none |
| LSET | addr11 | 0-2 | 0-2 | none |
| MOV | fr, #literal | 2 | 2 | none |
| fr1, fr2 | 2 | 2 | Z | |
| fr, W | 1 | 1 | none | |
| Option, #literal | 2 | 2 | none | |
| Option, fr | 2 | 2 | Z | |
| Option, W | 1 | 1 | none | |
| !port_fr, #literal | 2 | 2 | none | |
| !port_fr, fr | 2 | 2 | Z | |
| !port_fr, W | 1 | 1 | none | |
| W, #literal | 1 | 1 | none | |
| W, fr | 1 | 1 | Z | |
| W, /fr | 1 | 1 | Z | |
| W, fr-W | 1 | 1 | C, DC, Z | |
| W, ++fr | 1 | 1 | Z | |
| W, --fr | 1 | 1 | Z | |
| W, <<fr | 1 | 1 | C | |
| W, >>fr | 1 | 1 | C | |
| W, <>fr | 1 | 1 | none | |
| MOVB | bit1, bit2 | 4 | 4 | none |
| bit1, /bit2 | 4 | 4 | none | |
| MOVSZ | W, ++fr | 1 | 1/2 | none |
| W, --fr | 1 | 1/2 | none | |
| NEG | fr | 2 | 2 | Z |
| NOP | none | 1 | 1 | none |
| NOT | fr | 1 | 1 | Z |
| W | 1 | 1 | Z | |
| OR | fr, #literal | 2 | 2 | Z |
| fr1, fr2 | 2 | 2 | Z | |
| fr, W | 1 | 1 | Z | |
| W, #literal | 1 | 1 | Z | |
| W, fr | 1 | 1 | Z | |
| RET | none | 1 | 2 | none |
| RETW | literal1, literal2... | ? | 2 per | none |
| RL | fr | 1 | 1 | C |
| RR | fr | 1 | 1 | C |
| SB | bit | 1 | 1/2 | none |
| SC | none | 1 | 1/2 | none |
| SETB | bit | 1 | 1 | none |
| SKIP | none | 1 | 2 | none |
| SLEEP | none | 1 | 1 | TO, PD |
| SNB | bit | 1 | 1/2 | none |
| SNC | none | 1 | 1/2 | none |
| SNZ | none | 1 | 1/2 | none |
| STC | none | 1 | 1 | C |
| STZ | none | 1 | 1 | Z |
| SUB | fr, #literal | 2 | 2 | C, DC, Z |
| fr1, fr2 | 2 | 2 | C, DC, Z | |
| fr, W | 1 | 1 | C, DC, Z | |
| SUBB | fr, bit | 2 | 2 | Z |
| SWAP | fr | 1 | 1 | none |
| SZ | none | 1 | 1/2 | none |
| TEST | fr | 1 | 1 | Z |
| TEST | W | 1 | 1 | Z |
| XOR | fr, #literal | 2 | 2 | Z |
| fr1, fr2 | 2 | 2 | Z | |
| fr, W | 1 | 1 | Z | |
| W, #literal | 1 | 1 | Z | |
| W, fr | 1 | 1 | Z |
| Home | Products | Prices | Directory | Order | Contact | New | Books | Files | Links | Other |
mailto: don@dontronics.com_
Copyright © 1996-99 DonTronics
| Top of Page |