PC joystick to (Servo or Stepper) STEP/DIRECTION
I have found a need to read an old PC joystick and generate some movement commands.
The joystick is the older type with a 15 pin DB (DA actually) connector. Mine makes 3) 20k to 80k pots and 4 switch contacts available at the connector
My purpose is to drive a CNC gantry using servo or stepper motors (STEP and DIRECTION signals, not pulse length on hobby servos). The basics of manipulating the readings would be the same for use with a game or hobby servos and this may be useful to others.
Thd PIC will output X and Y movements based on the distance from center on the joystick with a small null in the center to prevent creep.
I have been unable to find anyone working on this with a search, so I will make a go of it and solicit any suggestions.
Since my joystick has 3 pots (X, Y, and trim) and 4 switches that can be read , the goal is to
grab the analog and determine if the X and Y are above or below center and set a direction pin to 1 or 0. Then the magnitude of the signal from center (minus the dead zone) will determine the number of pulses generated. The trim pot will modify the number of pulses and act kind of like a speed control or limit to tune the movement of the device. The switches will be used to activate extra functions on the device.
Reading the A/D should be straight forward, but I mulling how to control 2 pulse streams and adjust them using a third. Current target is a 18F2331 or 18F4431 and I'm going to see if I can get the PWM outputs to do what I want.
So far this is preliminary, so I'm just fishing to see if there is any interest. If there is, I will post my progress.
Bo
Seems no one can hear all the voices in my head....
Sorry about that, all of this made prefect sense in my head, I didn't do a very good job of explaining it to you.
The comment about Mach3 should have been clarified. The control panel will have a selector to determine if Mach3 will have the control or it will be given to the joystick exclusively. They will never share control and the joystick isn't meant to control through Mach3. The control panel may be shared with my mill in the future, in which case a pendant will make more sense. For now, the gantry isn't set up for heavy machining and just needs a semi-automated positioning. I think Henrik is correct, Mach3 has a provision for joystick input, but this is more an experiment in direct control with the PIC and joystick. Currently the application with the gantry and Mach3 may be masking the usefulness of this. Since Step/Direction controls are easily available and can be used with servos or steppers, there are a lot of uses for an independent control system that is very intuitive. I'm thinking animatronics and such too.
I have had a little time and started to study your thoughts and I agree, the main parts are there. I also am studying Walter's link to glean how it was done there. Sorry I don't have any code to share yet, I'm still working through the structure.
Bo
Trying to get the algorithm
What I'm not seeing is a way to connect the 0-500 A/D reading to the 165uS rollover of the timer.
Setting one of the timers so that at max Z, it will rollover at 165uS, I can adjust that timer with the Z to slow it down and get the "gain", ie: increase the time it takes to rollover
Reading the A/D, I can tell whether it is above 528 or below 500 and set direction.
Each of the 4 possible 0-500 values signify the number of those rollovers that generate pulses out.
Does this snippet look plausible?
Code:
IntCnt var word
ADval var word[3]
Holdoff var word[3]
'**** Interrupt ****************
IntCnt = IntCnt + 1
ADval[n]*12 = Holdoff[n]
if IntCnt = Holdoff[n] then Pulse
else NoPulse
endif
Pulse:
Pin[n] low ' drive a step pulse active
IntCnt = 0 ' clear count of interrupts
pauseus 2 ' delay to meet minimum time for drive
Pin[n] high ' return the pulse to inactive
return '
NoPulse:
return ' go back without sending a pulse
Bo
UNTESTED, but it compiles
Well, here's what I have so far:
I'm going to throw it on a LabX1 and see what happens.
Code:
'****************************************************************
'* Name : Joystick-StepDir.BAS *
'* Author : Mark A.Rokus *
'* Notice : Copyright (c) 2010 Controlled Surroundings Inc. *
'* : All Rights Reserved *
'* Date : 10/31/2010 *
'* Version : 1.0 *
'* Notes : *
'* Needs : Button routine and decel near limits *
'****************************************************************
' Read joystick and buttons and control Step/Direction drives.
' On power up, Beep and drive HOME, check centering of X & Y pots before
' enabling output. After ok, double beep, measure deflection, determine
' direction, and increase pulses.
' Z (trim) analog input modifies the timer preload to control speed
'*********************************************************************
' PC Joystick port. DB15
' 1: 5v
' 2: B1
' 3: X1 (0-100k)
' 4: GndB1
' 5: GndB2
' 6: Y1 (0-100k)
' 7: B2
' 8: 5v
' 9: 5v
' 10: B4
' 11: X2 (0-100k)
' 12: Gnd
' 13: Y2 (0-100k)
' 14: B3
' 15: 5v
' 18F4331/18F4431
' -------------------u------------------
' Vpp -1 |MCLR/vpp/RE3 RB7/KBI3/PGD|40- PGD
' X1 -2 |RA0/AN0 RB6/KBI2/PGC|39- PGC
' Y1 -3 |RA1/AN1 RB5/KBI1/PWM4/PGM|38- Man Enc B in
' Y2 -4 |RA2/AN2/Vr-/CAP1/Indx RB4/KBI0/PWM5|37- Man Enc A in
' Trav EncA in -5 |RA3/AN3/Vr+/CAP2/QEA RB3/PWM3|36- S4 Thumb
' Trav EncB in -6 |RA4/AN4/CAP3/QEB RB2/PWM2|35- S3 Down
' Beeper -7 |RA5/AN5/LVDIN RB1/PWM1|34- S2 Up
' Cut -8 |RE0/AN6 RB0/PWM0|33- S1 Trigger
' Pack -9 |RE1/AN7 Vdd+|32 +
' Stroke -10|RE2/AN8 Vss-|31- -
' + -11|AVdd RD7/PWM7 |30- Adir
' - -12|AVss RD6/PWM6 |29- Astep
' -13|OSC1/CLK1/RA7 RD5/PWM4 |28- Zdir
' -14|OSC2/CLK0/RA6 RD4/FLTA3 |27- Zstep
' Xlim+ -15|RC0/T1OSO/T1CKI RC7/RX/DT/SDO|26- RX
' Xlim- -16|RC1/T1OSI/CCP2/FLTA RC6/TX/CK/SS|25- TX
' Ylim+ -17|RC2/CCP1/FLTB RC5/INT2/SCK/SCL|24- Zlim-
' Ylim- -18|RC3/T0CKI/T5CKI/Int0 RC4/INT1/SDI/SDA|23- Zlim+
' Xstep -19|RD0/T0CKI/T5CKI RD3/SCK/SCL|22- Ydir
' Xdir -20|RD1/SDO RD2/SDI/SDA|21- Ystep
' |_____________________________________|
DEFINE OSC 40
DEFINE HSER_RCSTA 90h ' Enable serial port & continuous receive
DEFINE HSER_TXSTA 24h ' Enable transmit, BRGH = 1
DEFINE HSER_CLROERR 1 ' Clear overflow automatically
DEFINE HSER_SPBRG 173 ' 57600 Baud @ 40MHz, -0.22%
SPBRGH = 0
BAUDCON.3 = 1 ' Enable 16 bit baudrate generator
clear
beeper var PORTA.5
SwTrig var PORTB.0
SwUp var PORTB.1
SwDwn var PORTB.2
SwTmb var PORTB.3
XlimFar var PORTC.0
XlimNear var PORTC.1
YlimFar var PORTC.2
YlimNear var PORTC.3
ZlimFar var PORTC.4
ZlimNear var PORTC.5
Xs_out var PORTD.0 ' Step PULSE output to motor, Active LOW
Xd_out var PORTD.1 ' LEVEL output for direction
Ys_out var PORTD.2 ' Step PULSE output to motor, Active LOW
Yd_out var PORTD.3 ' LEVEL output for direction
Zs_out var PORTD.4 ' LEVEL output for direction
Zd_out var PORTD.5 ' Step PULSE output to motor, Active LOW
Ad_out var PORTD.6 ' LEVEL output for direction
As_out var PORTD.7 ' Step PULSE output to motor, Active LOW
Cut VAR PORTE.0
Pack var PORTE.1
Stroke var PORTE.2
Dir VAR byte ' Direction
Xdir var Dir.0
Ydir var dir.1
Zdir var Dir.2
Adir var Dir.3
Xch VAR WORD 'channels for A/D
Ych VAR WORD
Zch VAR WORD
Ach VAR WORD
Xspd var word
Yspd var word
IntCnt var word[2]
Holdoff var word[2]
Xpos var word ' counter for X position
Ypos var word ' counter for Y position
INCLUDE "DT_INTS-18.bas" ' Base Interrupt System
INCLUDE "ReEnterPBP-18.bas" ' Include if using PBP interrupts
'OPTION_REG = %00000000 ' Pull-up Enabled
T0CON = %10001000 ' no PS, PL65535 = .8uS, PL0 = 6.55mS
TRISA = %00011111
TRISB = $FF
TRISC = %10111111
TRISD = %00000000
TRISE = %00000000
PORTD = $00 ' start out HI. Moves on LOW pulse
'**** Analog setup ****
' Single shot, multi-channel, simultaneous Mode2, Groups A+B, then C+D
ADCON0 = %00011101
ADCON1 = %00010000 ' Set +/- Vref to AVdd/AVss, FIFO buffer enabled
ADCON2 = %11111111 ' Right justified, 64 Tad, Frc
ADCON3 = 0 ' Disable all triggers
ADCHS = %11000000 ' Channel select for AN0,1,2
ANSEL0 = %00000111 ' AN0,1,2 analog input, rest digital
'*********
ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler TMR0_INT, _PulseCk, PBP, yes
endm
INT_CREATE ; Creates the interrupt processor
ENDASM
goto Start
'============= SUBS ================================
'******** SUB: Get A/D readings *************************
GetAD:
ADCON0.1 = 1 ' Start conversion (Bruce t=6768)
WHILE ADCON0.1=1 ' Wait for it to complete (all 3channels)
WEND
Xch.HighByte = ADRESH ' get result from AN0
Xch.LowByte = ADRESL ' Increment buffer pointer
Ych.HighByte = ADRESH ' get result from AN1
Ych.LowByte = ADRESL ' Increment buffer pointer
Zch.HighByte = ADRESH ' get result fron AN2
Zch.LowByte = ADRESL ' Increment buffer pointer
Return
'******* SUB: Direction / Speed Check ***********************************
DirChk: 'get direction
if Xch > 513 then ' Above center
Xdir = 1
if Xch < 529 then ' below NULL,
Xspd = 0 ' No output
else
Xspd = Xch - 528 ' 528 TO 1028 - 528 = 0 TO 500
endif
endif
if Xch < 514 then
Xdir = 0
if Xch > 499 then
Xspd = 0
else
Xspd = 500 - Xch ' 500 - 499 TO 0 = 0 TO 500
endif
endif
if Ych > 513 then
Ydir = 1
if Ych < 529 then
Yspd = 0
else
Yspd = Ych - 528 ' 528 TO 1028 - 528 = 0 TO 500
endif
endif
if Ych < 514 then
Ydir = 0
if Ych > 499 then
Yspd = 0
else
Yspd = 500 - Ych ' 500 - 499 TO 0 = 0 TO 500
endif
endif
RETURN
'******* SUB: Home Routine ************************************
Home:
Xdir = 0 ' set home and clear Xpos and Ypos
Ydir = 0
TMR0H = 0 ' Set pulses @ 6.5mS (65535 preload)
TMR0L = 0
HomeX:
WHILE XlimNear = 0 ' Drive to Home X slowly
Xs_out = 0 ' drive a step pulse active
pauseus 3 ' delay to meet minimum time for drive
Xs_out = 1 ' return the pulse to inactive
wend
while XlimNear = 1
Xs_out = 0 ' drive a step pulse active
pauseus 3 ' delay to meet minimum time for drive
Xs_out = 1 ' return the pulse to inactive
wend
Xpos = 0 ' clear the counter to keep track of location
HomeY:
WHILE YlimNear =0 ' Drive to Home X slowly
Ys_out = 0 ' drive a step pulse active
pauseus 3 ' delay to meet minimum time for drive
Ys_out = 1 ' return the pulse to inactive
wend
while YlimNear = 1
Ys_out = 0 ' drive a step pulse active
pauseus 3 ' delay to meet minimum time for drive
Ys_out = 1 ' return the pulse to inactive
wend
Ypos = 0 ' clear the counter to keep track of location
return
'***** Initial setup ****************************************
Start:
while SwTmb =0
wend
beeper = 1 ' sound horn to warn of movement
pause 1000
beeper = 0
pause 1000
call Home ' initial homing & zeroing of position counters
ZeroStick:
call GetAD ' check 0 readings on joystick
while (Xch < 500) or (Xch > 527)or (Ych < 500) or (Ych > 527)
wend
beeper = 1 ' sound horn Done
pause 500
beeper = 0
pause 500
beeper = 1
pause 500
beeper = 0
pause 500
@ INT_ENABLE TMR0_INT ; enable Timer 0
'******* MAIN ***********************************************
Main:
call GetAD
TMR0H = Zch.HighByte ' setup speed by getting Z as preload
TMR0L = Zch.LowByte ' to Timer 0
call DirChk ' sets direction, speed, and null
' show results
hserout ["A/D X = ",DEC Xch," A/D Y = ",DEC Ych," A/D Speed = ",DEC Zch,13,10]
hserout ["Position X= ",DEC Xpos,", Y= ",dec Ypos,13,10]
GOTO Main
'******** Interrupt *******************************
PulseCk:
Holdoff[0] = (Xspd*12) ' 12 = approx step/bit ratio
if IntCnt[0] = Holdoff[0] then
goto PulseX
else
goto ChkY
endif
PulseX:
Xs_out = 0 ' drive a step pulse active
if Xdir = 1 then
Xpos = Xpos + 1
else
Xpos = Xpos - 1
Xs_out = 1 ' return the pulse to inactive
endif
IntCnt[0] = 0 ' clear count of interrupts
ChkY:
Holdoff[1] = Yspd*12
if IntCnt[1] = Holdoff[1] then
goto PulseY
else
goto NoPulse
endif
PulseY:
Ys_out = 0 ' drive a step pulse active
if Ydir =1 then
Ypos = Ypos + 1
else
Ypos = Ypos - 1
Ys_out = 1 ' return the pulse to inactive
endif
IntCnt[1] = 0 ' clear count of interrupts
NoPulse:
IntCnt[0] = IntCnt[0] + 1 ' keep track of times -
IntCnt[1] = IntCnt[1] + 1 ' through the interrupt
@ INT_RETURN '
;********* End of Interrupt **********************
END
The floor is open for comment....
Bo