There are many things you might want a microcontroller like the Stamp to read. Temperature, pressure, flow, light (or dark) are common measurements. Another common measurement is position. On a grand scale, GPS has made it easy to pinpoint your location on the Earth. However, sometimes you want to know where you are on a small scale, and to a very precise amount.

Why do you want to measure position? Robotics is one good example. Another is when you are building some sort of numerically controlled machine like a mill, drill press, or laser cutter. Traditionally, it has been expensive to accurately measure position on one axis and twice as expensive to measure two positions.

When we developed the PAK-VI, we meant for it to read PS/2 keyboards. However, we quickly found out that many people wanted to read PS/2 mice. These inexpensive devices are actually highly accurate position sensors. So many people were using the PAK-VI for this unintended purpose, that we decided we should make a special PAK just for that purpose. That PAK is the PAK-XI.

Mechanical mice have little wheels with slits in them. A pair of photosensors read the slits as they go by and use this information to resolve the position of the ball. Of course, you can couple anything you want to these wheels with a little ingenuity. Even better are the optical mice. These high tech devices take actual pictures of the surface the mouse is moving over and uses them to determine how far the mouse has moved. The PAK-XI will read these mice.

By the way, if you want to read a mouse with a PC, you could do it with a PAK-XI, but you will probably be happier with our GP-5 kit.

Where's Basic Stamp? (with Apologies to Waldo)

The PAK-XI uses RS232 communications. A simple program needs to issue a GETPKT or GETPKT2 command to read the 16-bit X and Y positions (GETPKT2 also clears the counters). Here is some very simple code excerpted from the main program below:

SEROUT OPIN,BAUD,[GETPKT]
SERIN IPIN\FPIN,BAUD,1000,timeout,[XRAW.HIGHBYTE, XRAW.LOWBYTE, YRAW.HIGHBYTE, YRAW.LOWBYTE, TB2]
DEBUG "x=", SDEC W4, "  y=", SDEC W5, " status=", HEX TB2, CR

The status byte tells you several things including the button state.

If all you want is raw counts, this is great. A typical mouse operates at 400 dots per inch when initialized by the PAK (although you can customize the initialization sequence). Displaying the measurement in inches or millimeters is a bit tricky because of the Stamp's integer math. Of course, you could use a PAK-I to add floating point math, but the Stamp alone is up to the task if you use a few tricks.

The "right" way to convert counts to inches is simply divide by 400. So 2000 counts is 5 inches. Of course, computing 1/400 is problematic with the Stamp. The first thing I did to make life easier was to make the numbers positive. I simply remember the original sign so I can display it later. After all, 400 counts is one inch regardless of direction.

My goal was to compute 2 parts to each number. Xinch will contain the X direction's whole number measurement. XFRAC will hold the fractional part. The FRACIN constant tells us how many inches are in one count. FRACIN is 25 which represents .0025. Suppose XRAW (the raw count) is 1. Then consider the following line:

XFRAC=(ABS(XRAW//CPIN)*FRACIN +5)/10   ' ABS not necessary here but included

This will compute the remainder after dividing by CPIN (400). That, of course, is 1 which is multiplied by FRACIN, leaving 25. The remainder of the equation rounds to 3 digits. So 25+5 = 30 and dividing by 10 gives 3 or .003.

It is trivial to compute the number of whole inches, like so:

' Compute whole number part (X)
Xinch=XRAW/CPIN

In this case, the largest remainder would be 399 and 399*25 = 9975. Even after rounding this is only 998. However, in the interest of making the code more general purpose, I decided to look for overflow in XFRAC and adjust Xinch if necessary:

' If XFRAC>=1000 then we have overflow and need to adjust whole number
IF XFRAC<1000 THEN XNOOV
  Xinch=Xinch+1
  XFRAC=XFRAC-1000
XNOOV:

The process is the same for the Y coordinate, of course. Conversion to millimeters requires us to multiply by 25.4. I actually did this in two different ways (although only one way appears in the code below). One way is easy to understand and the other is more elegant.

The easy way is to simply add the X numbers to themselves 254 times. On each addition, you can check for overflow and take appropriate steps. Here is the code in question:

Xmm=Xinch*254   ' 25.4 mm/inch
XFRACM=(Xmm//10)*1000  ' get the fractional part of Xmm
Xmm=Xmm/10 ' and then discard it
FOR TB1 = 1 TO 254
  XFRACM=XFRACM+XFRAC
  IF XFRACM<10000 THEN XMMCALC
  Xmm=Xmm+1
  XFRACM=XFRACM-10000
XMMCALC:
NEXT

If you want to satisfy yourself that this works, try mentally executing the code with Xinch=2 and XFRAC=500. Let the loop counter and the constant in the first line be 3 instead of 254 (so you are calculating 2.5 * 0.3):

Loop Xmm XFRACM
0 (before loop) 0 6000
1 0 6500
2 0 7000
3 0 7500

So the result is .6 + 0.015 or .7500!

However, I used a more elegant method (at least, I think it is more elegant). Remember how you did multiplication in school? Suppose you had 34 * 16. You'd compute 4*16 and 3*16 and add them together (shifting them to account for the digit position). Here's the code:

Xmm=Xinch*254   ' 25.4 mm/inch
XFRACM=(Xmm//10)*1000  ' get the fractional part of Xmm
Xmm=Xmm/10 ' and then discard it
TW=XFRAC*2  ' 254/100
TB3=2  ' 2nd digit
GOSUB DoMulDigitX
TW=XFRAC*5
TB3=1  ' 1st digit
GOSUB DoMulDigitX
TW=XFRAC*4
TB3=0    ' digit 0
GOSUB DoMulDigitX
. . .
' Helper for big multiplies (these use XRAW as temporary word)
DoMulDigitX:
LOOKUP TB3,[10000,1000,100],XRAW
Xmm=Xmm+(TW/XRAW)  ' add in whole part
LOOKUP TB3,[1,10,100],TB3
XFRACM=XFRACM+((TW//XRAW) *TB3)
IF XFRACM<10000 THEN NOFOV
  Xmm=Xmm+1
  XFRACM=XFRACM-10000
NOFOV:
RETURN

Notice that the 254 is separated out digit by digit in the above. The DoMulDigitX subroutine does the shifting and adding required. Consider the case where Xinch is 1 and XFrac is 200 (1.2):

Call with Xmm XFRACM
Before calls 25 4000
TW=400, TB3=2 29 4000
TW=1000, TB3=1 30 4000
TW=800, TB3=0 30 4800

The correct answer, then, is 30.48!

Armed with the whole and fractional parts, it is easy enough to print the result on an LCD, the debug terminal, or where ever you like.

Stamp Code

'{$STAMP BS2p}
' This code reads a PAK-XI and computes the inch and mm x & Y
' Pressing the left button zeros the count
' PAK-XI commands
PASSMODE CON 1    ' Only send position when asked (default)
ACTMODE  CON 2    ' Send position when button clicked (not very useful for Stamp)
CLRCTR   CON 3    ' Clear X,Y counters and button latch
CLRALL   CON 4    ' Same as CLRCTR but also clears input buffer
GETPKT   CON 5    ' Send X,Y, button latch packet  (5 bytes: Xhigh, Xlow, YHigh, Ylow, Status)
GETPKTZ  CON 7    ' Same as GETPKT but also clears counters
NORMMODE CON 9    ' Normal mode (same as pulling IMODE high), resets PAK
RAWMODE  CON 10   ' Raw mode (same as grounding IMODE), resets PAK
ESC      CON 11   ' Send next byte directly to mouse
INIT     CON 13   ' Initialize mouse
LATCH    CON 15   ' Read button latch and clear it
PAKRESET CON 255  ' Full reset
' Bits in status byte
YOVERMASK CON $80  ' Y overflow
XOVERMASK CON $40  ' X overflow
YSIGNMASK CON $20  ' Y Sign (1=negative)
XSIGNMASK CON $10  ' X Sign (1=negative)
MIDBMASK  CON 4    ' Middle button (note: some mice won't activate middle button w/o special commands)
RIGHTMASK CON 2    ' Right button
LEFTMASK  CON 1    ' Left Button
OPIN CON 3    ' Output to PAK
IPIN CON 2    ' Input from PAK
FPIN CON 13   ' Flow control
BAUD CON 240  ' Baud rate (9600) CHANGE FOR OTHER BS
CPIN CON 400 ' DPI of the mouse (could be 800 or 200)
FRACIN CON 25 ' .0025 inch per count
' Temporary words/bytes
TW VAR Word
TB0 VAR TW.HIGHBYTE
TB1 VAR TW.LOWBYTE
TB2 VAR Byte
TB3 VAR Byte
' X&Y Raw counts
XRAW VAR Word
YRAW VAR Word
' Whole number part (English)
Xinch VAR Word
Yinch VAR Word
' Fractional part (English)
XFRAC VAR Word
YFRAC VAR Word
' Whole number part (Metric)
XMM VAR Word
YMM VAR Word
' Fractional part (Metric)
XFRACM VAR Word
YFRACM VAR Word
' Signs
XSign VAR Byte
YSign VAR Byte

' ***** BEGIN
PAUSE 1500   ' wait for optical mouse to power up
GOTO start

' If we time out, try to restart
timeout:
DEBUG "*** TIMEOUT ***",CR

' ****** MAIN START
start:
' Assume chip is wired to start in normal mode (IMODE=1)
SEROUT OPIN,BAUD,[PAKRESET]  ' Reset everything
PAUSE 500  ' Wait for it to happen

top:
' Get position report
SEROUT OPIN,BAUD,[GETPKT]
SERIN IPIN\FPIN,BAUD,1000,timeout,[XRAW.HIGHBYTE, XRAW.LOWBYTE, YRAW.HIGHBYTE, YRAW.LOWBYTE, TB2]
'DEBUG "x=", SDEC W4, "  y=", SDEC W5, " status=", HEX TB2, CR
' Adjust signs (" " for nonnegative, "-" for negative)
XSIGN=" "
YSIGN=" "
IF XRAW.BIT15=0 THEN SignY
XSIGN="-"
XRAW=-XRAW
SignY:
IF YRAW.BIT15=0 THEN Calc
YSIGN="-"
YRAW=-YRAW
Calc:
'DEBUG CLS,"x=", DEC XRAW, " y=", DEC YRAW,CR
' Compute English first - the mouse seems to be most accurate in inches
' Get fractional part (X and Y) and round to 3 digits
XFRAC=(ABS(XRAW//CPIN)*FRACIN +5)/10
YFRAC=(ABS(YRAW//CPIN)*FRACIN +5)/10
' Compute whole number part (X)
Xinch=XRAW/CPIN
' If XFRAC>=1000 then we have overflow and need to adjust whole number
IF XFRAC<1000 THEN XNOOV
  Xinch=Xinch+1
  XFRAC=XFRAC-1000
XNOOV:
' Same logic for Y
Yinch=YRAW/CPIN
IF YFRAC<1000 THEN YNOOV
  Yinch=Yinch+1
  YFRAC=YFRAC-1000
YNOOV:
' Display English
DEBUG CLS,"x=", XSIGN, DEC Xinch,".",DEC3 XFRAC,"in y=",YSIGN,DEC Yinch,".",DEC3 YFRAC,"in",CR
' Convert English to metric
Xmm=Xinch*254   ' 25.4 mm/inch
XFRACM=(Xmm//10)*1000  ' get the fractional part of Xmm
Xmm=Xmm/10 ' and then discard it
' We want to say XFRACM=XFRAC*254 but this would overflow
' since 999 * 254 = 253,746
' I have used two methods to do this:
' 1 - We multiply each digit by hand (x*2, x*5, x*4)
' and accumulate the answer correctly (see DoMulDigitX)
' 2 - You can add x 254 times and watch for overflow
' This is easier to understand, but perhaps not
' as clean and elegant
TW=XFRAC*2  ' 254/100
TB3=2  ' 2nd digit
GOSUB DoMulDigitX
TW=XFRAC*5
TB3=1  ' 1st digit
GOSUB DoMulDigitX
TW=XFRAC*4
TB3=0    ' digit 0
GOSUB DoMulDigitX
XFRACM=(XFRACM+5)/10     ' round back to 3 digits
' Handle cases like .9995 which rounds to 1
IF XFRACM<1000 THEN NOOVXM
  Xmm=Xmm+1
  XFRACM=XFRACM-1000
NOOVXM:
' Same logic for Y
Ymm=Yinch*254
YFRACM=(Ymm//10) *  1000
Ymm=Ymm/10
TW=YFRAC*2  ' 254/100
TB3=2
GOSUB DoMulDigitY
TW=YFRAC*5
TB3=1
GOSUB DoMulDigitY
TW=YFRAC*4
TB3=0
GOSUB DoMulDigitY

YFRACM=(YFRACM+5)/10
IF YFRACM<1000 THEN NOOVYM
Ymm=Ymm+1
YFRACM=YFRACM-1000
NOOVYM:
' OK we are done! Show metric
DEBUG "x=", XSIGN, DEC Xmm,".",DEC3 XFRACM,"mm y=",YSIGN,DEC Ymm,".",DEC3 YFRACM,"mm",CR
' read button latch
SEROUT OPIN,BAUD,[LATCH]
SERIN IPIN\FPIN,BAUD,[TB2]
'DEBUG "Btn latch=", HEX TB2,CR
IF TB2 & LEFTMASK <> LEFTMASK THEN waitsome  ' if left button, clear counters
SEROUT OPIN,BAUD,[GETPKTZ]
SERIN IPIN\FPIN,BAUD,1000,timeout,[XRAW.HIGHBYTE, XRAW.LOWBYTE, YRAW.HIGHBYTE, YRAW.LOWBYTE, TB2]
waitsome:
PAUSE 1000   ' Time between samples
GOTO top

' Helper for big multiplies (these use XRAW as temporary word)
DoMulDigitX:
LOOKUP TB3,[10000,1000,100],XRAW
Xmm=Xmm+(TW/XRAW)  ' add in whole part
LOOKUP TB3,[1,10,100],TB3
XFRACM=XFRACM+((TW//XRAW) *TB3)
IF XFRACM<10000 THEN NOFOV
  Xmm=Xmm+1
  XFRACM=XFRACM-10000
NOFOV:
RETURN

DoMulDigitY:
LOOKUP TB3,[10000,1000,100],XRAW
Ymm=Ymm+(TW/XRAW)  ' add in whole part
LOOKUP TB3,[1,10,100],TB3
YFRACM=YFRACM+((TW//XRAW) *TB3)
IF YFRACM<10000 THEN NOFOVY
  Ymm=Ymm+1
  YFRACM=YFRACM-10000
NOFOVY:
RETURN

Site contents © 1997-2018 by AWC, Houston TX    (281) 334-4341