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