Home  Products Prices Directory Order Contact New Books Files Links Other
DonTronics Home Page Announces the public release of the Scott Edwards Electronics PIC Source Book/Disk.
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? 
Don's Download Dungeon
 

Scott Edwards Electronics, Inc.
 
Scott Edwards Electronics Inc.
2700 E. Fry Blvd Suite A4 Sierra Vista, AZ 85635
phone 520-459-4802; fax 520-459-0623
e-mail info@seetron.com

The PIC Source Book
A collection of ready-to-use assembly language routines based on the instruction set of the BASIC Stamp ® single-board computer.

by Scott Edwards, 1994


Foreword to Online Edition

The PIC Source Book was written in 1993 to provide users of the Parallax PIC programming tools with a ready-made collection of assembly code in Parallax assembly mnemonics. At the time, there were four PICs: 16C54, 55, 56, and 57. The popular '84 and ADC-equipped '71 were brand new. Since then, Microchip has made dozens of architectural enhancements to the PICs, and made its assembler available free via the net and CD-ROM. This has diminished interest in both the 5x PICs and the Parallax assembler.

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.

Useful Links

Scott Edwards Electronics, Inc.
Company founded by the author; makers of serial LCD products.
Parallax, Inc.
Makers of tools for programming PIC and SX microcontrollers.
Microchip Technology Inc.
Manufacturers of PIC microcontrollers.
Microchip Code Converted PIC Source Book Anyone??
Parallax to MicroChip Conversion.

On the public release of the Scott Edwards PIC Source Book Disk, all executable files have been removed, and there is no Technical support whatsoever with the on line version.

 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.

Table of Contents

Introduction 
Branch 
Button 
Debug 
EEPROM 
End 
For...Next 
Gosub/Return 
Goto 
High
If...Then 
Input 
Let x=x+y 
Let x=x-y 
Let x=y/z (division) 
Let x=y//z (remainder) 
Let x=y*z 
Lookdown 
Lookup 
Low
Nap 
Output 
Pause 
Pot 
Pulsin 
Pulsout 
Pwm 
Random 
Read 
Reverse
Serin (receive data) 
Serin (convert # data) 
Serin (check qualifiers) 
Serout (send data) 
Serout (format # data) 
Sleep 
Sound 
Toggle 
Write 
Parallax Instructions 

 

Introduction

This book is a collection of useful program routines and examples written in Parallax assembly language for the PIC 16C5x series of microcontrollers. With minor changes, these routines may be adapted to the entire PIC 16Cxx series.

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:

  1. BASIC Stamp users who want to program the PIC directly in Parallax assembly language, but don't want to leave familiar programming methods behind.
  2. PIC programmers who are looking for a cookbook of routines to speed program development.
  3. PIC programmers who see advantages in prototyping with the Stamp interpreter before moving to the PIC for the final product.
Whichever group you belong to, there are a few prerequisites to using this book effectively. You should have the Microchip and Parallax literature on the PIC hardware and not be afraid to use it. This book is not padded with the manufacturers' data sheets and instructions.

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.

Differences from PBASIC.

These routines were developed by examining what each of the PBASIC instructions does, and then writing assembly code to duplicate its function. In some cases, functionality has been changed slightly to better suit the needs of assembly-language programmers.

Manipulation of the Data-Direction Register.

Some of PBASIC's I/O instructions automatically change a pin's data direction when they execute and then restore it when they are done. The PBASIC environment permits this by keeping a copy of the PIC's tristate (TRIS) register in normal read/write memory. The routines in this book do not work that way. Comments tell you how TRIS should be set up, but the routines do not modify it directly. This avoids causing mysterious bugs as a result of improper TRIS bookkeeping outside the routines. It also keeps the routines small, since many programs do not require any TRIS trickery.

Pin and Port Independence (PPI).

This is one of the great features of the BASIC Stamp--the ability to apply any I/O command to any pin of the Stamp's single I/O port. This goes hand-in-hand with the TRIS manipulations discussed above. All of the I/O routines in this book follow this convention by letting you specify which pin and port to use. Because they don't modify TRIS, it is your program's responsibility to set the port pins to input or output as appropriate.

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.

Merging Routines.

To use the routines presented here, you'll need to merge them, either with each other, or with your own code. This is a straightforward cut-and-paste operation, but can require a little thought and planning. Here are a few hints:

Structure of the Routines.

Most of the code presented in this book is in the form of callable subroutines. While this makes it easy to incorporate the routines into your PIC programs, it also makes it likely that you'll run into two limitations of the PIC architecture; restriction of call destinations to the first half of program-memory pages, and the two-level stack.

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.

 

Using this Book with Non-Parallax Tools.

I feel that Parallax assembly language is clearer and more productive than the original Microchip dialect. That's why I use it in my programming, and why I used it to write this book. However, many of you have other PIC tools--most notably the PICStart 16B from Microchip. You can use the Parallax assembler PASM to assemble code for use with these tools. Just add the switch /S after the file name. This suppresses the generation of Parallax-specific configurat ion data (device type, wdt, oscillator, and code-protection settings). PICStart doesn't accept this data. To assemble Serin for a non-Parallax programmer, you'd type:
PASM SERIN /S
If you forget to use the /S, you'll get an address-out-of-range message from your programming software. If you use the /S switch and still get an error message, check the device type in the assembly-language source listing. It may be set for a PIC with more program memory than the one you're programming. For example, the device directive says "16C56" when you are programming a 16C54. If that's the case, evaluate the program to determine whether it will work in a '54. If it will, change the device directive, reassemble the code, and try again.


Branch

jumps to a routine based on the value in the w register.

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.

Demonstrating Branch.

To see Branch in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run BRANCH.SRC. When you press switches corresponding to one of the patterns that causes Branch to jump to the routine Action (0111, 0101 or 0011), the LED will blink. Any other pattern of switch closures will be ignored.

; 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.

Button

debounces input from a switch and jumps to a routine, optionally performing an autorepeat function if the switch is held down.

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.

Demonstrating Button.

To see Button in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run BUTTON.SRC. When you press the switch, the LED will blink. Hold the switch down and, after a brief delay, the LED will blink rapidly.

; 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

displays the contents of internal registers as an aid to understanding, correcting, or modifying programs.

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:


EEPROM

reads, writes, and erases a 256-byte serial EEPROM.

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.

Demonstrating EEPROM.

To see EEPROM in operation, connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run EEPROM.SRC. When you apply power to the PIC, it will write a scanning pattern to the bits of the EEPROM, then read this pattern back onto the LEDs.

; 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

End

stops execution of a program and puts the PIC to sleep until reset or watchdog timeout.

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.


For...Next

repeats a set of instructions according to repeated addition or subtraction operations on a control variable.

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. 
NEXT
The 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
NEXT
Although 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
NEXT
PBASIC'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 :loop
The 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 :loop
Note 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 :loop
If 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 100
This 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.


Gosub/Return

jumps to a subroutine address after storing the current address on a stack in memory. When Return is invoked, the program resumes at the line following the latest Gosub.

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.

Demonstrating Gosub.

To see Gosub in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run GOSUB.SRC. When you apply power to the PIC, the LEDs will light up one at a time, demonstrating that the program executed all three nested subs and returned to the main program.

; 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.

Goto

loads a 9-bit address into the program counter and resumes program execution from that address.

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.


High

writes a high (1) to the specified pin.

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.

Demonstrating High.

To see High in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run HIGH.SRC. When you apply power to the PIC, the LEDs will light up one at a time until all are lit.

; 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

If...Then

compares two 16-bit numbers by subtraction and reports one of the three possible cases: n1 < n2; n1 > n2; and n1 = n2.

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 < n2
Since 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.

Demonstrating Comp16.

To see Comp16 in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run COMP16.SRC. When you apply power to the PIC, the LEDs will light up in the binary pattern returned by the comparison (in this case 01, where 1 represents a lit LED). Try various combinations of n1 and n2 to see the three codes.

; 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.

Input

changes a pin's data direction to input.

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.


Let x = x + y (addition)
Let x = x - y (subtraction)

adds or subtracts 16-bit values to produce a 16-bit result, permitting overflow.

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.

 

Demonstrating ADD16 and SUB16.

To see these routines in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run ADD_SUB.SRC. When you apply power to the PIC, the LEDs will light up in the binary pattern of the answer to the math problem C836h - 2575h, which is A2C1h. That works out to the following binary pattern on the LEDs:
1010 0010 1100 0001
Try 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

Let x1 = y / z (division)
Let x2 = y // z (division, remainder)

divides one 16-bit value into another 16-bit value to compute a 16-bit result and remainder.

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.

Demonstrating Divide.

To see Divide in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run DIVIDE.SRC. When you apply power to the PIC, the LEDs will light up in the binary pattern of the answer to the math problem 330Ah/00A3h, which is 50h with a remainder of 1Ah. So the quotient (50h) displays the following binary pattern on the LEDs:

 

0000 0000 0101 0000
Try 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

Let x = y * z (multiplication)

multiplies two 16-bit values into a 16-bit product, permitting overflow.

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:

Although the product of two 16-bit numbers is a 32-bit number, note that in this case the first 16 bits of the product are all 0s and can be safely ignored.

Demonstrating Multiply.

To see Multiply in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run MULTIPLY.SRC. When you apply power to the PIC, the LEDs will light up in the binary pattern of the answer to the math problem 0Bh * 1016h, which is B0F2h. The product displays the following binary pattern on the LEDs:
1011 0000 1111 0010
Try 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

searches for a key value in a table and returns its position number.

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.

Demonstrating Lookdown.

To see Lookdown in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run LOOKDOWN.SRC. When you apply power to the PIC, the LEDs will light in the pattern 0110, representing the number 6, the position of G in the table.

; 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.

Lookup

finds a table entry corresponding to an index number.

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.

Demonstrating Lookup.

To see Lookup in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run LOOKUP.SRC. When you apply power to the PIC, the display will count upward in hexadecimal from 0 to F.

; 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.

Low

writes a low (0) to the specified pin.

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.

Demonstrating Low.

To see Low in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run LOW.SRC. When you apply power to the PIC, the LEDs, initially lit, will go out one at a time until all are dark.

; 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

Nap

puts the controller to sleep for a period of 18 milliseconds to 2.3 seconds.

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.

Demonstrating Nap.

To see Nap in operation, connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run NAP.SRC. When you apply power to the PIC, the LED will toggle at a rate corresponding to the nap period; slightly more than a second. Try changing the value passed to Nap (in the routine main) to adjust the flashing rate.

; 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

Output

changes a pin's data direction to output.

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.


Pause

puts the controller into a "do-nothing" delay loop for 1 to 65535 units of 1000 instruction cycles (milliseconds at 4 MHz).

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.

Demonstrating Pause.

To see Pause in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run PAUSE.SRC. When you apply power to the PIC, the LED will toggle once per second (assuming a 4 MHz clock). Try changing the value passed to Pause to adjust the flashing rate.

; 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.

Pot

measures a variable resistance.

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.

Pot works by fully charging the capacitor, then measuring the time required to discharge it through the series resistance. This time is proportional to the resistance of the pot. The listing shows the technique. The Pot I/O pin is set up as an output. Then the capacitor is fully charged by making the Pot pin high for a period equal to 5 x R x C, where R is the maximum resistance of the pot, and C is the value of the capacitor. In this case, that is 5 x (10 x 103) x (0.1 x 10-6) = 5 x 10-3 seconds or 5 milliseconds.

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.

Demonstrating Pot.

To see Pot in operation, connect the circuits above and below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run POT.SRC. When you adjust the pot, the binary value displayed on the LEDs will change.

; 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.

Pulsin

measures the width of a pulse in 10-instruction-cycle units. Returns a 16-bit value and an error code.

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:

Pulsin, 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. The same restriction applies if you decide to use a jump table (see Branch) to process the error code in w; the destinations of the calculated jumps must be in the first half of a program memory page.

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.

Demonstrating Pulsin.

To see Pulsin in operation, connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run PULSIN.SRC. When you adjust the function generator, the binary value displayed on the LEDs will change.

 


; 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

produces a pulse by inverting the state of the specified pin for a period of 1 to 65,535 units of 10 instruction cycles.

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.

Demonstrating Pulsout.

To see Pulsout in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run PULSOUT.SRC. The 'scope will show a series of 100-µs pulses (assuming a 4-MHz clock for the PIC).

 


; 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

Pwm

generates a bit stream of a specified duty cycle for digital-to-analog conversion.

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.

The Pwm command generates variable duty-cycle waveforms from 0% to 100% in steps of 0.392% (1/255). Each time it is called, it outputs 256 pwm cycles with the specified proportion of highs to lows.

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.

Demonstrating Pwm.

To see Pwm in operation, connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run PWM.SRC. The LED, initially off, will gradually glow brighter until it reaches full brightness. It will abruptly turn off, then repeat the process.

; 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

Random

generates a 16-bit pseudorandom number.

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:

A circuit like this generates a sequence of bits that repeats every 65,535 shifts. Because of the long interval between the same bit pattern showing up, the sequence can, for many purposes, be considered random. To use Random, just call it. Upon return, the bits previously held in hiB and lowB will have been clocked through the equivalent of the circuit above. You can then use hiB or lowB, or the two combined into a 16-bit value, as your random number. You may also use a bit of one of the variables as a coin-toss decision maker. Random can be used to generate white-noise effects. Call the routine from within a tight program loop and copy one bit of hiB or lowB to an output pin. The stream of random bits at the output produces a rushing sound in a connected speaker or amplifier.

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.

Demonstrating Random.

To see Random in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run RANDOM.SRC. The LEDs will show the sequence of pseudorandom patterns as they cycle through the variable lowB.

; 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,<





Read

read a byte of data from the EEPROM.

Please see the program listing under EEPROM. 


Reverse

reverses a pin's data direction.

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 (receive data)

receives a byte or string of bytes serially at 300 to 2400 bps (4 MHz clock). Optionally filters incoming data to a particular range of ASCII characters, such as numbers.

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.

Demonstrating Serin.

To see Serin in operation, connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run SERIN.SRC. 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). Type a few characters and watch the LEDs display the binary equivalents of the received data. To check out the filtering capability, modify the program to set the filter bit, then reassemble and rerun it. Now the LEDs will respond only when you press the number keys 0 through 9.

; 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

Serin (convert # data)

converts a numeric text string received by Serin into a 16-bit number.

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       1
Serin 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.

Demonstrating ASC_BIN.

To see ASC_BIN in operation, you may run it on the PSIM simulator as suggested in the program listing. For a live demonstration, connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Program the PIC or downloader with the program assembled from the file SERIN_#.SRC on the accompanying disk. 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). Now type in some numbers and text and watch the LEDs interpret the data as described on the previous page.

;
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

Serin (check qualifiers)

determines whether text received by Serin matches a table of qualifiers (password).

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.

Demonstrating Qualify.

To see Qualify in operation, you may run it on the PSIM simulator as suggested in the program listing. For a live demonstration, connect the circuit shown below to an erasable PIC or PIC emulator, such as the Parallax downloader. Program the PIC or downloader with the program assembled from the file SERIN_Q.SRC on the disk. 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 dat a bits, 1 stop bit (N81). Now type in some numbers and text. Nothing happens until you type the password "Sesame". Then the LED toggles.

; 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 (send data)

transmits a byte or string of bytes serially at 300 to 2400 bps (4 MHz clock).

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.

Demonstrating Serout.

To see Serout in operation, connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run SEROUT.SRC. 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 letters "OK" will appear on the PC screen.

; 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.

Serout (format # data)

converts a 16-bit number into a counted string for transmission by Serout.

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          65535
By 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.

 

Demonstrating BIN_ASC.

To see BIN_ASC in operation, run it on the PSIM simulator. When the program finishes executing, look at the locations 18h through 1Fh. BIN_ASC will have built a counted string of ASCII characters representing the input value (in decimal) with an inserted decimal point. In the case of the demo, the string will be "655.35" which will display as bytes 36h, 35h, 35h, 2Eh, 33h, and 35h. The string count (6) will appear in location 1Fh.

 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

Sleep

puts the controller to sleep for a period of 1 to 65535 units of 2.3 seconds.

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.

Accuracy of Sleep.

In the discussion of Nap, we ignored the issue of accuracy. For most applications, small errors in the length of a nap are not an issue. With Sleep, it could be a different story. If you construct a data logger and expect it to take a reading every 12 hours on the dot by using Sleep as an interval timer, you're in for a disappointment.

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.

Demonstrating Sleep.

To see Sleep in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC. (The downloader won't work properly in this case, because Sleep relies on a power-on reset occurring. The downloader is reset by MCLR.) Assemble and program the PIC with SLEEP.SRC. Every 2 hours (approximately) the state of the LED will toggle. To change the period, change the numbers stored in hiB and lowB in the portion of the program labeled main.

; 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

Sound

produces a note of specified frequency and duration.

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):

The formula shows that freq sets the period of the tone in 14-µs units. As a result, the frequency isn't a linear function of freq. For example, a freq of 1 produces a 280.1-Hz tone, while 2 produces 281.2 Hz. At the higher end of the scale, a freq of 250 produces an 11.9-kHz tone, while 251 produces 14.29 kHz.

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.

Demonstrating Sound.

To see Sound in operation, connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run SOUND.SRC. When you apply power to the PIC or reset the emulator, the speaker will play a short tune.

; 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

Toggle

inverts the state of an output pin.

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.

Demonstrating Toggle.

To see Toggle in operation, either run the program with the PSIM simulator, or connect the circuit below to an erasable PIC or PIC emulator, such as the Parallax downloader. Assemble and run TOGGLE.SRC. When you apply power to the PIC, the LEDs connected to ports RB and RC will be inverted one-by-one, beginning with RB.0.

; 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

Write

write a byte of data to the EEPROM.

Please see the program listing under EEPROM.


Parallax Instruction Chart

The chart below lists all of the Parallax instructions, their operands, number of program words, instruction cycles, and side effects.

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 squash these bugs before they bite by keeping a copy of the chart close at hand while you program. For example, if you plan to use one of the skip instructions, make sure that the instruction to be skipped is one program word long.

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
 

DonTronics Home Page

mailto: don@dontronics.com_
Copyright © 1996-99 DonTronics
 
 
 
Top of Page