[Home]Using A Joypad To Jog And Control Spindle Speeds

LinuxCNCKnowledgeBase | RecentChanges | PageIndex | Preferences | LinuxCNC.org

Difference (from prior major revision) (no other diffs)

Changed: 4c4
A much simpler way to use a joypad is described here: http://wiki.linuxcnc.org/cgi-bin/emcinfo.pl?Simple_Remote_Pendant
A much simpler way to use a joypad is described here: http://wiki.linuxcnc.org/cgi-bin/wiki.pl?Simple_Remote_Pendant

hal_joystick has been depreciated from EMC 2.4

Note that halui now has analog jog inputs so there is no need to use the sim-encoder components A much simpler way to use a joypad is described here: http://wiki.linuxcnc.org/cgi-bin/wiki.pl?Simple_Remote_Pendant However this page stil contains useful information

In the previous section, we looked at how to use a basic joypad configuration. There is a lot more you can do with a joypad. This shows how to create a one-shot jog with your joypad and how to link in some halui functions like spindle control. If you look closely, you will notice that this joypad.hal is based upon the previous chapter's joypad.hal.

The comments in this file should be enough to explain the system. Remember that this probably won't work on your computer until you change the parameters to match your specific joypad.

# JOYPAD.HAL
# This HAL file configures the joypad to run a CNC mill with manual control. Normally, the two joysticks
# will move the three axes freely. When one of the buttons is pressed, it will shift to a jog mode and 
# move once in the direction of the pressed axis. 

# This file may seem daunting at first, but if you read just one section, then it will be much easier.

# We will use hal_joystick to read the axis value (float) for X Y Z, we will send these values to the 
# speed pin of a sim-encoder component, for X Y Z, this component outputs Phase-A and Phase-B signal, 
# just like a real quadrature rotary encoder. We will decode those signals with an encoder component for X Y Z
# and will send the result counts value to the axis jog pin for X Y Z.

#----------------------------------------------------------------------------------------
# Load joystick into thread
#----------------------------------------------------------------------------------------
#Updated to use the newer hal_input with the faster machine. Dual here refers to a 
#unique identifier for the particular joypad that we're using. Check YOUR computer for
#a unique identifier for YOUR joypad.
loadusr -W hal_input Dual

#The joypad mapping is as follows:
#Joypad button vs. EMC2 map:
#1		input.0.btn-trigger
#2		input.0.btn-thumb
#3		input.0.btn-thumb2
#4		input.0.btn-top
#5		input.0.btn-top2
#6		input.0.btn-pinkie
#7		input.0.btn-base  (Note that there is no "1" suffix)
#8		input.0.btn-base2
#9		input.0.btn-base3
#10		input.0.btn-base4
#LAD		input.0.btn-base5 (Push in Left Analog Stick)
#RAD		input.0.btn-base6

#There are other mappings - from the terminal window, type "halrun -I" then load the 
#"loadusr -W hal_input Dual" command, then use "show" to see all the inputs. To check
#the mapping, enter "loadusr halmeter -s pin input.0.btn-base" (as an example). In order
#to see what button or axes does what, you will have to waggle the joystick and write 
#it down. There's no other way.


#----------------------------------------------------------------------------------------
# Load required components
#----------------------------------------------------------------------------------------
#If you're familiar with another programming language, this is (sort of) an #include 
#section. We'll require some number of components. It starts at 0-index, so if you ask
#for count=6, you get .0 to .5
loadrt mux2 count=8
loadrt flipflop count=2
loadrt and2 count=2
loadrt not count=5
loadrt encoder num_chan=3 
loadrt sim_encoder num_chan=3 
loadrt conv_s32_bit count=6
loadrt conv_bit_s32 count=6
loadrt conv_s32_float count=10
loadrt wcomp count=6
loadrt edge count=3
loadrt deadzone count=6
loadrt offset count=3

#----------------------------------------------------------------------------------------
# Mapping Requirements: COMMENTS ONLY - THERE IS NO CODE IN THIS SECTION.
#----------------------------------------------------------------------------------------
# If you're setting up your own system, then ask the machinists what buttons should be
# doing which function. 
# [BUTTON-SAMPLES]
# The requested button functions are as follows:
# Painted button	function		mapping
#	1 		spindle stop		input.0.btn-trigger
#	2		spindle slow		input.0.btn-thumb
#	3		spindle start		input.0.btn-thumb2
#	4		spindle faster		input.0.btn-top
#	5		set jog scale: 1/1000	input.0.btn-top2
#	6		go into jog mode	input.0.btn-pinkie	
#	7		set jog scale: 4/10000	input.0.btn-base
#	8		machine on		input.0.btn-base2
#	9		coolant on		input.0.btn-base3
#	10		estop			input.0.btn-base4

	
#----------------------------------------------------------------------------------------------------------
# Deadband Setup
#----------------------------------------------------------------------------------------------------------
# The joypad is a little jittery, so we're adding a deadzone. It's a $20 Logitech gaming pad, not a 
# $1500 optical unit designed for precision milling. 

# THE DEADBAND HAS GREATLY IMPROVED MILLING QUALITY ON OUR MACHINE

# The units for the axes change because there are two ways of measuring the axes. Since we have to
# measure the axes twice, we have to use both. The first section is float, the second is s32. If you
# use halmeter to check the axis values, the reason for these values will become very clear.
# Note that calibration will not let you get away without a deadband. See above - it's a $20 joypad.
setp deadzone.0.center 		0
setp deadzone.0.threshhold	.2
setp deadzone.1.center 		0
setp deadzone.1.threshhold	.2
setp deadzone.2.center 		0
setp deadzone.2.threshhold	.2

# These units are different because a different measuring system is used.
# Note that these will be converted to floats.
setp deadzone.3.center 		127
setp deadzone.3.threshhold	10
setp deadzone.4.center 		127
setp deadzone.4.threshhold	10
setp deadzone.5.center 		127
setp deadzone.5.threshhold	10

# Map the raw axes to the deadzones so the deadzones have inputs.
net Xjoydead deadzone.0.in => input.0.abs-x-position
net Yjoydead deadzone.1.in => input.0.abs-y-position
net Zjoydead deadzone.2.in => input.0.abs-rz-position

#Counts are in s32, and deadzone requires float. Convert.
net Xjogconv conv-s32-float.3.in => input.0.abs-x-counts 
net Yjogconv conv-s32-float.4.in => input.0.abs-y-counts 
net Zjogconv conv-s32-float.5.in => input.0.abs-rz-counts 

net Xjogdead deadzone.3.in => conv-s32-float.3.out 
net Yjogdead deadzone.4.in => conv-s32-float.4.out
net Zjogdead deadzone.5.in => conv-s32-float.5.out


	
#----------------------------------------------------------------------------------------------------------
# Axis movement mode - freewheeling or one-shot jog
#----------------------------------------------------------------------------------------------------------
# We will have the jog speed set up later. The order isn't relevant since human reaction speed
# is so much lower than the thread duration.

# Speaking of thread duration, it is **critical** that the edge pulse width is greater than the duration
# of the thread that we're inserting these calculations into. The servo-thread is 400000, so the 
# edge is set to 10x that. This gives us a nice, long pulse which is easily detectable by the thread and
# is also usable in other calculations. You can lower this if you'd like, but you'll find that you'll get
# intermittent jogging. It will move once every 2-3 presses instead of every press. It does NOT change the
# jog amount - it's still a one-shot with the width set below in the jog speed section.

# Set up the one-shot pulses. This will allow a jog at just one increment.  
# 127 is the midpoint, 1 is min, 255 is max. Window is 100 - 150
setp wcomp.0.min 		100
setp wcomp.0.max 		150
setp wcomp.1.min 		1
setp wcomp.1.max 		105
setp edge.0.in-edge 		FALSE
setp offset.0.offset 		-1
setp edge.0.out-width-ns        4000000

setp wcomp.2.min 		100
setp wcomp.2.max 		150
setp wcomp.3.min 		1
setp wcomp.3.max 		105
setp edge.1.in-edge 		FALSE
setp offset.1.offset 		-1
setp edge.1.out-width-ns        4000000

setp wcomp.4.min 		100
setp wcomp.4.max 		150
setp wcomp.5.min 		1
setp wcomp.5.max 		105
setp edge.2.in-edge 		FALSE
setp offset.2.offset 		-1
setp edge.2.out-width-ns        4000000

#------------------------------------------------------
# Set up X axis one-shot
#------------------------------------------------------
# To set up the jogs as a one-shot, we have to build a small Rube Goldberg machine.
# A window comparator will tell you if there IS a trigger, not which direction the 
# pulse should go. That's where the invert output comes in. 

# If the second comparator shows that we're in the lower half, then we offset the 
# inverse of the pulse wave to get a negative pulse. We also use that second comparator
# to trigger the mux to determine whether to send a positive of negative pulse.

# All these conversions are required because the required components will only work with
# certain types of inputs. Edge detection sends out a bit, and a mux requires a float.
net Xwindow wcomp.0.in => deadzone.3.out
net Xwindow wcomp.1.in => deadzone.3.out
net XwindowInv not.0.in => wcomp.0.out
net Xedge edge.0.in => not.0.out
net Xupconv1 conv-bit-s32.0.in => edge.0.out
net Xupconv2 conv-s32-float.0.in => conv-bit-s32.0.out

net Xupconvinv1 conv-bit-s32.1.in => edge.0.out-invert
net Xupconvinv2 conv-s32-float.2.in => conv-bit-s32.1.out
net Xupconvinv3 offset.0.in => conv-s32-float.2.out

net Xjogleft mux2.3.in0 => conv-s32-float.0.out
net Xjogright mux2.3.in1 => offset.0.out
net XjogDirection mux2.3.sel => wcomp.1.out
net Xjoglink mux2.0.in1 => mux2.3.out
#At this point, mux2.0 has the one-shot Xpulse as one of the inputs. 

#------------------------------------------------------
# Set up Y axis one-shot
#------------------------------------------------------
# To set up the jogs as a one-shot, we have to build a small Rube Goldberg machine.
# A window comparator will tell you if there IS a trigger, not which direction the 
# pulse should go. That's where the invert output comes in. 

# If the second comparator shows that we're in the lower half, then we offset the 
# inverse of the pulse wave to get a negative pulse. We also use that second comparator
# to trigger the mux to determine whether to send a positive of negative pulse.

# All these conversions are required because the required components will only work with
# certain types of inputs. Edge detection sends out a bit, and a mux requires a float.
net Ywindow wcomp.2.in => deadzone.4.out
net Ywindow wcomp.3.in => deadzone.4.out
net YwindowInv not.1.in => wcomp.2.out
net Yedge edge.1.in => not.1.out
net Yupconv1 conv-bit-s32.2.in => edge.1.out
net Yupconv2 conv-s32-float.6.in => conv-bit-s32.2.out

net Yupconvinv1 conv-bit-s32.3.in => edge.1.out-invert
net Yupconvinv2 conv-s32-float.7.in => conv-bit-s32.3.out
net Yupconvinv3 offset.1.in => conv-s32-float.7.out

net Yjogleft mux2.4.in0 => conv-s32-float.6.out
net Yjogright mux2.4.in1 => offset.1.out
net YjogDirection mux2.4.sel => wcomp.3.out
net Yjoglink mux2.1.in1 => mux2.4.out
#At this point, mux2.1 has the one-shot Ypulse as one of the inputs. 

#------------------------------------------------------
# Set up Z axis one-shot
#------------------------------------------------------
# To set up the jogs as a one-shot, we have to build a small Rube Goldberg machine.
# A window comparator will tell you if there IS a trigger, not which direction the 
# pulse should go. That's where the invert output comes in. 

# If the second comparator shows that we're in the lower half, then we offset the 
# inverse of the pulse wave to get a negative pulse. We also use that second comparator
# to trigger the mux to determine whether to send a positive of negative pulse.

# All these conversions are required because the required components will only work with
# certain types of inputs. Edge detection sends out a bit, and a mux requires a float.
net Zwindow wcomp.4.in => deadzone.5.out
net Zwindow wcomp.5.in => deadzone.5.out
net ZwindowInv not.2.in => wcomp.4.out
net Zedge edge.2.in => not.2.out
net Zupconv1 conv-bit-s32.4.in => edge.2.out
net Zupconv2 conv-s32-float.8.in => conv-bit-s32.4.out

net Zupconvinv1 conv-bit-s32.5.in => edge.2.out-invert
net Zupconvinv2 conv-s32-float.9.in => conv-bit-s32.5.out
net Zupconvinv3 offset.2.in => conv-s32-float.9.out

net Zjogleft mux2.5.in0 => conv-s32-float.8.out
net Zjogright mux2.5.in1 => offset.2.out
net ZjogDirection mux2.5.sel => wcomp.5.out
net Zjoglink mux2.2.in1 => mux2.5.out
#At this point, mux2.2 has the one-shot Zpulse as one of the inputs. 


# We are setting up button6 to be the mode selector. While held down, it will change the output of 
# the multiplexor. While released, the three axis controllers will act as normal. In other words, 
# the change will be in the loading section, before the axes are mapped to the encoder speeds.
net axisMode input.0.btn-pinkie => mux2.0.sel
net axisMode input.0.btn-pinkie => mux2.1.sel
net axisMode input.0.btn-pinkie => mux2.2.sel

net XnormalInput deadzone.0.out => mux2.0.in0
net YnormalInput deadzone.1.out => mux2.1.in0
net ZnormalInput deadzone.2.out => mux2.2.in0


# Create links between the axis pins and the speed pin of the sim-encoder for X Y Z
net velX mux2.0.out => sim-encoder.0.speed
net velY mux2.1.out => sim-encoder.1.speed
net velZ mux2.2.out => sim-encoder.2.speed


#-------------------------------------------------------
# After this point, there is no change to the two modes.
#-------------------------------------------------------
# Create links between sim-encoder Phase-A and Phase-B and encoder Phase-A and Phase-B for X Y Z
net XA sim-encoder.0.phase-A => encoder.0.phase-A
net XB sim-encoder.0.phase-B => encoder.0.phase-B
net YA sim-encoder.1.phase-A => encoder.1.phase-A
net YB sim-encoder.1.phase-B => encoder.1.phase-B
net ZA sim-encoder.2.phase-A => encoder.2.phase-A
net ZB sim-encoder.2.phase-B => encoder.2.phase-B

# Create links between encoder counts and jog counts for X Y Z
net countX encoder.0.counts => axis.0.jog-counts
net countY encoder.1.counts => axis.1.jog-counts
net countZ encoder.2.counts => axis.2.jog-counts

# Set parameter values
setp encoder.0.position-scale           1
setp encoder.0.x4-mode         		TRUE
setp encoder.1.position-scale           1
setp encoder.1.x4-mode         		TRUE
setp encoder.2.position-scale           1
setp encoder.2.x4-mode         		TRUE
setp encoder.capture-position.tmax      0
setp encoder.update-counters.tmax       0
setp sim-encoder.0.ppr     		00000064
setp sim-encoder.0.scale            	-1
setp sim-encoder.1.ppr     		00000064
setp sim-encoder.1.scale            	1
setp sim-encoder.2.ppr     		00000064
setp sim-encoder.2.scale            	-1
setp sim-encoder.make-pulses.tmax       0
setp sim-encoder.update-speed.tmax      0

# Enable jog for X Y Z
setp axis.0.jog-enable 			TRUE
setp axis.1.jog-enable 			TRUE
setp axis.2.jog-enable 			TRUE


#----------------------------------------------------------------------------------------------------------
# Scale button - Set jog speed
#----------------------------------------------------------------------------------------------------------
# First, we select the two buttons for speed selection. We'll use 5 and 7 for 1/1000 and 4/10000, respectively.
net button5 input.0.btn-top2 => flipflop.0.reset
net button7 input.0.btn-base => flipflop.0.set
net button5 input.0.btn-top2 => flipflop.1.reset
net button7 input.0.btn-base => flipflop.1.set

#Add the control for the mux by mapping it to the flip-flop. Thus, it will stay in the last state until it is 
#changed.
net chosenJogSpeed flipflop.0.out => mux2.6.sel
net chosenJogDuration flipflop.1.out => mux2.7.sel

#Now, map the jog scale values to the axes.
net jogscale mux2.6.out => axis.0.jog-scale
net jogscale mux2.6.out => axis.1.jog-scale
net jogscale mux2.6.out => axis.2.jog-scale

# Set parameters values
setp flipflop.0.tmax         	3750
setp mux2.6.tmax         	3601

# Set the two scale values. The output will equal in0 when FALSE and in1 when TRUE.
setp mux2.6.in0          	0.001
setp mux2.6.in1          	0.0004


#----------------------------------------------------------------------------------------------------------
# Power on machine
#----------------------------------------------------------------------------------------------------------
# In this section, we provide a simple link to turn the machine on (i.e. press the orange button in AXIS)
net JoypadMachineOn input.0.btn-base2 => halui.machine.on


#----------------------------------------------------------------------------------------------------------
# Activate spindle and change its speed.
#----------------------------------------------------------------------------------------------------------
# In this section, we will link the spindle to the joypad and have its speed set up to increase or decrease
# based on other buttons. 
#	1 		spindle stop		input.0.btn-trigger
#	2		spindle slow		input.0.btn-thumb
#	3		spindle start		input.0.btn-thumb2
#	4		spindle faster		input.0.btn-top

# 
# We don't want to connect the joypad directly to the spindle. Instead, we set up the halui signal
# so that we trigger the spindle from inside the fancy halui routines.
net JoypadSpindleStart input.0.btn-thumb2 => halui.spindle.start
net JoypadSpindleStop input.0.btn-trigger => halui.spindle.stop
net JoypadSpindleFaster input.0.btn-top => halui.spindle.increase
net JoypadSpindleSlower input.0.btn-thumb => halui.spindle.decrease


#----------------------------------------------------------------------------------------------------------
# Activate coolant
#----------------------------------------------------------------------------------------------------------
# A single joypad button will start and stop the coolant. This is where the logic will get away from you
# if you blink. Drawing it on paper will help.
# The most important part is that there are two and2 gates being used here.

# We are mapping flood on to one AND gate, and flood off (NOT flood on) to another AND gate.
net flood-is-on halui.flood.is-on => and2.0.in0
net flood-is-on halui.flood.is-on => not.3.in
net not-flood-is-on not.3.out     => and2.1.in0

# Now we can map the buttons (base3, or button 9) to the other input of each gate.
net button3 input.0.btn-base3 => and2.0.in1
net button3 input.0.btn-base3 => and2.1.in1

# At this point, one gate (0) is flood & button9; the other (1) is !flood & button9.
# So if it's not flooding and you press the button, it will start.
# If it is flooding and you press the button, it will stop.
net floodOn and2.1.out => halui.flood.on
net floodOff and2.0.out => halui.flood.off


#----------------------------------------------------------------------------------------------------------
# Software-based estop
#----------------------------------------------------------------------------------------------------------
net button10  input.0.btn-base4 => halui.estop.activate

#----------------------------------------------------------------------------------------------------------
# Function Loading
#----------------------------------------------------------------------------------------------------------
# At the end, we add all the functions and parameters we have defined into the threads. Note that the order
# is somewhat important. If you are updating the position based on a vlue, you must update the value then
# update the position or you'll update based on old data. Likewise, if you are updating a value, make sure
# that the values you are using for the calculation are updated. THIS IS RECURSIVE - in other words, make
# sure that those values are, in turn, calculated based on updated values.

# The first components to add are the deadzone calculations. This is because they basically interact directly
# with the raw hardware values.
addf deadzone.0 servo-thread
addf deadzone.1 servo-thread
addf deadzone.2 servo-thread
addf deadzone.3 servo-thread
addf deadzone.4 servo-thread
addf deadzone.5 servo-thread
addf conv-s32-float.3 servo-thread
addf conv-s32-float.4 servo-thread
addf conv-s32-float.5 servo-thread

#And now we add the position and jog conversions. 
addf wcomp.0 servo-thread
addf wcomp.1 servo-thread
addf wcomp.2 servo-thread
addf wcomp.3 servo-thread
addf wcomp.4 servo-thread
addf wcomp.5 servo-thread

addf edge.0 servo-thread
addf edge.1 servo-thread
addf edge.2 servo-thread

addf not.0 servo-thread
addf not.1 servo-thread
addf not.2 servo-thread

addf offset.0.update-output servo-thread
addf offset.1.update-output servo-thread
addf offset.2.update-output servo-thread

addf conv-s32-float.0 servo-thread
addf conv-s32-float.1 servo-thread
addf conv-s32-float.2 servo-thread
addf conv-s32-float.6 servo-thread
addf conv-s32-float.7 servo-thread
addf conv-s32-float.8 servo-thread
addf conv-s32-float.9 servo-thread

addf mux2.3 servo-thread
addf mux2.4 servo-thread
addf mux2.5 servo-thread

addf conv-s32-bit.0 servo-thread
addf conv-s32-bit.1 servo-thread
addf conv-s32-bit.2 servo-thread
addf conv-s32-bit.3 servo-thread
addf conv-s32-bit.4 servo-thread
addf conv-s32-bit.5 servo-thread

addf conv-bit-s32.0 servo-thread
addf conv-bit-s32.1 servo-thread
addf conv-bit-s32.2 servo-thread
addf conv-bit-s32.3 servo-thread
addf conv-bit-s32.4 servo-thread
addf conv-bit-s32.5 servo-thread

# Update the last set of multiplexors before the encoder
addf mux2.0 servo-thread
addf mux2.1 servo-thread
addf mux2.2 servo-thread

# Now that the positioning data is up to date, we update the encoders, both real and simulated. 
addf encoder.capture-position servo-thread
addf sim-encoder.update-speed servo-thread
addf encoder.update-counters base-thread
addf sim-encoder.make-pulses base-thread

# Now we can update the speed values. They aren't that critical in terms of calculation order, since the user will have to 
# look down, press the button, then resume. The computer will react millions of times faster than even the twitchiest 
# gamer / machinist.
addf flipflop.0 servo-thread
addf mux2.6 servo-thread
addf flipflop.1 servo-thread
addf mux2.7 servo-thread

# Coolant is the slowest possible thing we can add due to the lag and the pump.
addf and2.0 joy-thread
addf and2.1 joy-thread
addf not.3 joy-thread


LinuxCNCKnowledgeBase | RecentChanges | PageIndex | Preferences | LinuxCNC.org
This page is read-only. Follow the BasicSteps to edit pages. | View other revisions
Last edited July 7, 2012 1:09 pm by Archivist (diff)
Search:
Published under a Creative Commons License