' Model 423 Dual Controller, Robot Calculator and XYZ Box ' $Id: m423.bas,v 1.12 2003/04/15 01:08:17 tomj Exp $ ' This supports the Robot Calculator and the XYZ box to drive the Tek ' storage scope or the analog plotter. Runs on a Micromint ' PICSTIC installed in a WPS Model 01 card. ' Type V records ' -------------- ' Type V records drive the robot attached to a Victor calculator. ' It's very simple; no motor control is needed, so essentially this ' just translates ASCII characters to key presses, and delays a ' fixed amount per key: ' character function delay time ' 0 - 9 digits 200mS ' + add 300mS ' - subtract 400mS ' ' The solenoids need a low duty cycle or they will get hot. It's probably ' difficult to overheat them driven by the Model 3, but they get hot at ' max. duty cycle from /dev/ttyS0. ' Type X records ' -------------- ' Accepts simple motion commands from a serial port (see below) to move ' up/down and left/right, and generates X and Y voltages. Also does pen ' up/down as Z. The commands are assumed to be produced by a Postscript ' compiler. The command set is compatible with stepper-motor functions; ' eg. the command to "move left 1 step" will increment the output voltage ' by one bit-value. ' ' The command stream can be pre-processed to convert long repetitive ' streams of identical commands into a command/repeat-command sequence. ' No bounds-checking is done, since it's such a serious hit on performance, ' and more easily done on the host computer. ' ' COMMANDS are identical to the sample "Flutterwumper" commands in ' Don Lancaster's flutools.ps; single characters (the ASCII digits "0" ' through "9"). CTS is used in the usual way to indicate ready for ' the next command. ' ' 0=E, 1=NE, 2=N, 3=NW, 4=W, 5=SW, 6=S, 7=SE. ' Simple motion commands; NE, NW, SW, SE are functionally ' identical to N then E, etc, but faster. ' 8 Pen up (Z bit = 0V). ' 9 Pen down (Z bit = 5V). ' : Home; move to 0,0 (0 volts out X and Y). ' ; no operation. ' c Set pen up/down delay time, milliseconds. Default is PENDELAY. ' ?c Repeat previous motion command, 'c' * 100 times. Same ' as < command except for long runs. ' ' A NOTE ON STEP DELAY: ' ' Step delay applies only to repeated commands. For single commands, ' the infrastructure overhead is enough of a delay for the HP ' plotter, likely the slowest device we'll ever use. For repeated ' commands, step delay is used. ' Copyright Tom Jennings, 2002-2003. symbol SPEED= N2400 ' RS-232 serial line speed ' ASCII characters symbol NUL= 0 symbol SOH= 1 symbol STX= 2 symbol ETX= 3 symbol BEL= 7 symbol LF= 10 ' ASCII line feed symbol CR= 13 ' ASCII carriage return symbol SO= 14 ' ASCII SO: turn motor off immediately symbol SI= 15 ' ASCII SI: delay 2 seconds ' storage symbol bits0= b0 ' where bits live symbol indata= bit0 ' serial protocol symbol pend= b1 ' pend up/down delay, symbol c= b2 symbol i= b3 symbol k= b4 ' symbol j= b5 symbol machine= b7 ' which machine, V or X symbol serflag= b8 ' (unsigned) serinp() control/status ' These bits are mapped to Port B pins. symbol lamps= b9 symbol XLAMP= %10000000 ' bit7 = X lamp, symbol ONLAMP= %01000000 ' bit6 = ON lamp, symbol VLAMP= %00100000 ' bit5 = V lamp, symbol LAMPM= %00011111 ' mask off lamp bits, ' Robot Calculator data symbol solenoids= w5 ' solenoid bits, accessed as... symbol Wlo= b10 ' ... LS bits (A outputs) symbol Whi= b11 ' ... MS bits (B outputs) symbol n= w6 symbol ADD_SOL= 1024 ' ADD solenoid bit -- MUST MATCH TABLE! ' Key-press timing: ' down up total ' digit 0 - 9 NUMDOWN NUMUP NUMDOWN+NUMUP ' + - NUMDOWN NUMUP+ADDUP NUMDOWN+NUMUP+ADDUP symbol NUMDOWN= 100 ' number press down time, symbol NUMUP= 300 ' number press up (release) time, symbol ADDUP= 200 ' additional + or - release time, ' XYZ data symbol cmd= b14 ' previous command symbol stepd= b15 ' step delay parm symbol X= w8 ' X value (16 bits) symbol Xlo= b16 ' X LS byte symbol Xhi= b17 ' X MS byte symbol Y= w9 ' Y value (16 bits) symbol Ylo= b18 ' Y LS byte symbol Yhi= b19 ' Y MS byte symbol NOP= 255 ' NO OP command; out of range ' These work for the HP 7035B. symbol STEPDELAY= 6 ' mS per step delay default symbol PENDELAY= 150 ' pen up/down delay, symbol HOMEDELAY= 250 ' max. home time ' common IO symbol SD= 0 ' serial data pin/DAC DIN symbol SRA= 1 ' SR A clock (HH, MO) symbol SRB= 2 ' SR B clock (MM, DD) symbol DACCLK= 3 ' DAC SCLK symbol DACCS= 4 ' DAC /CS symbol PEN= 5 ' ploter pen, symbol PENpin= pin5 ' plotter pen, symbol CTS= 7 ' ASCII Clear To Send symbol CTSpin= pin7 ' ASCII Clear To Send symbol RXD= 6 ' ASCII serial input symbol RXDpin= pin6 ' ASCII serial input ' ' Turn the solenoids off quickly since the outputs float high. ' ' 76543210 1=output 0=input pins= %00010000 ' all off except /CS, dirs= %10111111 ' set I/O directions, ' Turn off all solenoids, and set DAC outputs to zero V. ' This routine is special purpose and faster than the general- ' purpose calls. call clrhw ' clear hardware, ' Init global data; some of the low-level routines need ' it now. ' solenoids= 0 ' solenoids off, ' X= 0 : Y= 0 ' DACs at home, ' cmd= NOP ' no prev. command, stepd= STEPDELAY ' plotter step time, pend= PENDELAY ' pen up/down time, ' Here we interweave lamp test and clearing the calculator. lamps= XLAMP : call outb : pause 500 ' solenoids off, solenoids= ADD_SOL ' press ADD key, lamps= VLAMP : call out : pause NUMDOWN solenoids= 0 ' release all keys, lamps= ONLAMP : call out : pause 500 ' calc cleared. ' ==================================================================== ' ' WPS Protocol state machine. ' call serini ' init serial input. ' Ground state: return here on error or EOT. s0: lamps= ONLAMP : call outB ' only ON on, ' Await SOH. s1: call serinw : if c <> SOH then s1 ' wait for SOH, ' Await . s2: call serinw ' await character, machine= c ' remember type, if c <> "V" then s2a : lamps= VLAMP : goto s3 s2a: if c <> "X" then s1 : lamps= XLAMP ' Now's a good time to light the right lamp. s3: call outB ' turn on recd type lamp, ' Await STX. s4: indata= 0 ' await STX ("start of data") ' s5 handles the STX data... ETX sequence. ' NUL protocol failure, restart ' SOH nonsense, ignore ' STX data follows, eaten ' ETX end of data, ' EOT end of block, restart ' all data passed to handler s5: call serinw ' process char stream, branch c, (s0, s5, s6, s4, s0) ' 0:eh 1:eh 2:STX 3:ETX 4:EOT ' ' Falling through the branch means it's not a stream-control character; ' if indata is true, then it's payload data. If it's false, then ' we're waiting for STX. ' if indata = 0 then s5 ' ignore data before STX ' Dispatch to the right handler. if machine = "V" then execV execX: gosub xyz : goto s5 ' "X" execV: gosub kachunk : goto s5 ' "V" ' STX: data follows this character until ETX (or error). s6: indata= 1 : goto s5 ' data will follow, ' ==================================================================== ' ---- Robot Calculator -------------------------------------- ' ' Translate character C into solenoid motion. Pretty ' much a straightforward lookup. ' ' Output bit mappings: L=lamp bit (set elsewhere) ' port B port A func decimal ' 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 ' L L L 0 0 0 1 0 0 0 0 0 0 0 0 0 "0" 512 ' L L L 0 0 0 0 0 1 0 0 0 0 0 0 0 "1" 128 ' L L L 0 0 0 0 0 0 1 0 0 0 0 0 0 "2" 64 ' L L L 0 0 0 0 0 0 0 1 0 0 0 0 0 "3" 32 ' L L L 0 0 0 0 0 0 0 0 1 0 0 0 0 "4" 16 ' L L L 0 0 0 0 0 0 0 0 0 1 0 0 0 "5" 8 ' L L L 0 0 0 0 0 0 0 0 0 0 1 0 0 "6" 4 ' L L L 0 0 0 0 0 0 0 0 0 0 0 1 0 "7" 2 ' L L L 0 0 0 0 0 0 0 0 0 0 0 0 1 "8" 1 ' L L L 0 0 0 0 1 0 0 0 0 0 0 0 0 "9" 256 ' L L L 0 0 1 0 0 0 0 0 0 0 0 0 0 "+" 1024 ' L L L 0 1 0 0 0 0 0 0 0 0 0 0 0 "-" 2048 ' ' A key is pressed, held for some period, released; after release the ' mechanism calculates. Timing is very ad hoc. There is no subtlety here; ' a solenoid presses a key for a fixed period, released, fixed delay. "+" ' and "-" have longer delays. That's it. ' kachunk: lamps= VLAMP : call outB if c <> "+" then ka1 : c= ":" ' 1st char after "9" ka1: if c <> "-" then ka2 : c= ";" ' 2nd char after "9" ka2: c= c - "0" ' "01234567890+-" becomes 0-11 ' If C is out of range, it falls through the lookup() with ' current value of solenoids= 0. lookup c, (512, 128, 64, 32, 16, 8, 4, 2, 1, 256, 1024, 2048), solenoids call out ' turn on selected solenoid, pause NUMDOWN ' short delay for 0 - 9, ka3: solenoids= 0 : call out ' deselect all solenoids, pause NUMUP ' key release and operate, if c < 10 then ka4 : pause ADDUP ' calculations are slow, ka4: return ' --- Type X, Plotter ------------------------------------------------ ' Optimize for the common case; a single motion command. Next ' is a repeated command. Home, up down, etc are less sensitive. ' We don't do much error-checking here. XYZ: c= c - "0" ' remove ASCII offset, xyzr: branch c, (east, ne, north, nw, west, sw, south, se, up, down, home, xyret, repeat, steptime, pentime, bigrep) ' Exit for errors and non-repeatable commands; set the ' command to NOP and set the repeat count to 1. xyret: n= 1 ' stops repeat loop, cmd= NOP ' forget prev. command, return ' return. ' Repeat the previous command. repeat: call getrpt ' get repeat count, r0: c= cmd ' command to repeat, r1: gosub xyzr ' do command, pause stepd ' pause between steps, n= n - 1 : if n > 0 then r1 return ' Repeat the previous command, times 100. bigrep: call getrpt n= n * 100 ' like I said, big, goto r0 ' Home: Go home as fast as possible. home: call up ' lift the pen, X= 0 : Y= 0 : call outDAC ' home the plotter, pause HOMEDELAY ' wait for home. goto xyret ' Set step and pen up/down time. steptime: gosub getrpt : stepd= n : goto xyret pentime: gosub getrpt : pend= n : goto xyret ' Get the repeat-count character, subtract "0", return in N. getrpt: call serinw : n= c - "0" : return ' Pen up/down. Pause before, because the plotter might still be slewing. down: pause stepd: PENpin= 1 : pause pend : goto xyret up: pause stepd: PENpin= 0 : pause pend : goto xyret end ' basic ASM ; ; Motion commands. +X=north, -X=south; +Y=east, -Y=west. ; These save the command just executed (C) in CMD. ; _south movlw 255 ; add -1 to LS byte, addwf _Ylo btfss C ; if (no) carry, decf _Yhi ; decrement MS byte, movlw 3 ; bound to 10 bits andwf _Yhi, f goto _movret _north incf _Ylo ; increment low byte, btfsc Z incf _Yhi ; increment high if lo=0 goto _movret _east incf _Xlo btfsc Z incf _Xhi goto _movret _west movlw 255 ; add -1 to LS byte, addwf _Xlo btfss C ; if (no) carry, decf _Xhi ; decrement MS byte, movlw 3 ; bound to 10 bits andwf _Xhi, f goto _movret _ne incf _Ylo ; do north btfsc Z incf _Yhi incf _Xlo ; then east btfsc Z incf _Xhi goto _movret _se incf _Xlo ; do east btfsc Z incf _Xhi movlw 255 ; then south addwf _Ylo btfss C decf _Yhi movlw 3 andwf _Yhi, f goto _movret _nw incf _Ylo btfsc Z incf _Yhi movlw 255 addwf _Xlo btfss C decf _Xhi movlw 3 andwf _Xhi, f goto _movret _sw movlw 255 ; do south addwf _Ylo btfss C decf _Yhi movlw 3 andwf _Yhi, f movlw 255 addwf _Xlo btfss C decf _Xhi movlw 3 andwf _Xhi, f ; ; Move the DACs to the new position, save the current ; command for a possible later repeat, and execute ; the step delay, if set. ; _movret movf _C, w ; save command movwf _CMD ; for later repeat, call _outDAC ; make the plotter move, ; movf _stepd, w ; delay time, ; iorwf 0 ; if a step delay is set, ; btfss Z ; ; goto pause@0W ; delay for servo. goto done ; ; Serial input routines. These build upon the library routines that ; come with the compiler. All routines deassert CTS when a character ; is received (since we have no buffer). ; ; Assert CTS, wait for a character. _serinw call _serinp ; assert CTS, check for char, btfss _serflag, 0 ; if serflag LSB = 0, goto _serinw ; keep waiting. return ; Assert CTS, check for a character by seeing if a start ; bit is present; if so, drop CTS, receive the character ; into C, set serflag. _serinp bcf _serflag, 0 ; clear serflag, bsf portB, _CTS ; assert CTS, btfss portB, _RXD ; if Rx bit false goto done ; return, no char avail. call serin@W ; else read the char, bcf portB, _CTS ; deassert CTS, movwf _C ; store char in C, bsf _serflag, 0 ; set serflag= 1. goto done ; Initialize the serial input routine; pass the Rx pin to the ; PIC library routine, deassert CTS, clear our flag and return. _serini bcf portB, _CTS ; deassert CTS, movlw _RXD ; Rx pin number, call BP@Pin movlw _SPEED ; port speed, movwf GOP ; (local to ass'y code?) clrf _serflag ; set serflag to zero. goto done ; Output X and Y to the DACs. Refer to the MAX504 datasheet; each chip, ; wired in series, wants 16 bits total; four 0-fill bits, 10 data bits, ; two dummy LSBs, two trailing bits to round to 16. The dummy LSBs for ; X and the leading 0's for Y are output in one call. ; ; Modifies J, I. ; _outDAC bcf portB, _DACCS ; assert DAC /CS, movf _Xhi, w movwf _J ; X MSBs to J, upper bits 0's, rlf _J rlf _J ; now four 0's precede X MSBs, movlw 6 ; 4 fill + 2 data, call _outJ ; output those, movf _Xlo, w movwf _J movlw 8 call _outJ ; output 8 LSBs, ; The X DAC still needs two dummy LSBs; we'll output them ; when we issue Y MSBs. movf _Yhi, w movwf _J ; Y MSBs to J, upper bits 0's, movlw 8 call _outJ ; output 2 dummy LSBs, 4 fill, 2 data, movf _Ylo, w movwf _J movlw 8 call _outJ ; output 8 Y LSBs, clrf _J movlw 2 ; now final 2 dummy LSBs. call _outJ bsf portB, _DACCS ; deassert /CS goto done ; ; Output (reg. W) bits of (J) to the DAC, starting ; with the MSB. ; Modifies I, J. ; _outJ movwf _I ; loop counter, _ok bcf portB, _SD ; data=0... rlf _J ; (move MSB to carry to test) btfsc status, 0 ; ...unless MSB is 1... bsf portB, _SD ; ...data=1. bsf portB, _DACCLK bcf portB, _DACCLK ; clock it decfsz _I goto _ok goto done ; ; Disable all output devices as quickly as possible; remove ; drive from the solenoids and output 0V at the DACs. This ; could be done by the slower but more general: ; ; solenoids= 0 : call _out ; X= 0 : Y= 0 : call outDAC ; ; And could be dispensed with if space gets tight. ; ; Modifies J, K. ; _clrhw bcf portB, _PEN ; lift the plotter pen, bsf portB, _SD ; 1=no sol drive bcf portB, _DACCS ; assert DAC /CS movlw 8 movwf _J ; for 1st loop, movlw 24 movwf _K ; for 2nd loop, ; This loop clears SRA, SRB and the 1st 8 bits of the DACs. _ca1 bsf portB, _SD ; 1=no sol. drive, bsf portB, _SRA ; clock both SRs bcf portB, _SRA bsf portB, _SRB ; at once, bcf portB, _SRB bcf portB, _SD ; 0= no DAC output, bsf portB, _DACCLK ; clock DACs, bcf portB, _DACCLK decfsz _J goto _ca1 ; Clear the remaining bits of the DACs. _ca2 bsf portB, _DACCLK bcf portB, _DACCLK decfsz _K ; loop count K set at top. goto _ca2 bsf portB, _DACCS ; deassert DAC /CS goto done ; ; Outputs the solenoid bits to ports A and B. Solenoid ; bits must be inverted, since the 2003's drive OC drivers. ; Outputs Wlo to the A outputs and Whi to the B outputs. ; A0-A7, B0-B1 are calc solenoids; B2-B7 are lamps, etc. ; Modifies K, J. ; _out comf _Wlo, w ; Wlo inverted --> W, movwf _K ; put in temp, movlw 8 movwf _J ; set bit loop counter, _oa1 bcf portB, _SD ; set data bit= 0 rlf _K ; move MSB to carry, btfsc status, 0 ; if carry set (MSB == 1) bsf portB, _SD ; assert a 1, bsf portB, _SRA ; clock data bit out, bcf portB, _SRA decfsz _J ; repeat. goto _oa1 ; Same as above, but outputs MS solenoid bits plus lamps. ; This is also a faster entry point for outputting the ; lamp bits only; it requires that Whi be cleared, which ; it will be whenever the calculator is not actively ; operating. ; _outB comf _Whi, w ; Whi inverted --> W, andlw _LAMPM ; clear lamp bits, iorwf _lamps, w ; add in lamps, movwf _K ; store in temp, movlw 8 ; otherwise movwf _J ; yup, pretty much _ob1 bcf portB, _SD ; the same thing rlf _K ; as above. btfsc status, 0 ; figure it out. bsf portB, _SD bsf portB, _SRB bcf portB, _SRB decfsz _J goto _ob1 goto done ; ; Initialize PA3 as an output, PA4 as an input. ; _PAinit movf 133, w ; read (WHAT)? iorlw 16 ; PA3 is input, andlw 247 ; PA4 is output, movwf 133 ; write reg. return ; ; Set PA3 on and off. ; _PA3lo movf 5, w ; read PA andlw 247 ; clear PA3 movwf 5 return _PA3hi movf 5, w ; read PA iorlw 8 ; set PA3 movwf 5 return ; ; Return in N the state of PA4 (0 or 1). ; _PA4in movf 5, w ; read PA, andlw 16 ; mask it off, movwf _N ; store in N, rrf _N rrf _N rrf _N rrf _N ; shift to bit 0 return endasm