Logitech Dual Action Gamepad as EMC2 Pendant

Gamepad Pendant
Gamepad Pendant

Just got this working and it’s downright slick!

The general idea:

The Hat jogs X and Y at the current maximum speed.

The Left Knob jogs X and Y proportionally to the Knob displacement.

The Right Knob jogs Z (Up-Down) and A (Left-Right) proportionally to the Knob displacement.

Press either Knob downward to toggle the maximum jog speed between MAX_LINEAR_VELOCITY (as defined in the Sherline.ini file) and 5% of that value. The slow speed is useful for creeping up on alignment points: the first active level of the joysticks runs at a nose-pickin’ pace.

The left little button (labeled 9) switches to Manual mode, although the AXIS display does not update to indicate this. Same as “F3” on keyboard, minus the GUI update.

The right little button (labeled 10) continues a G-Code program by activating the Resume function. Same as “S” on the keyboard.

The Mode button switches the functions of the Hat and Left Knob. That button does not generate an output and the Mode cannot be controlled programmatically. Swapping those functions doesn’t seem particularly useful in this application, so the LED should never be ON.

Buttons 1-4 are not used for anything yet.

On the back:

  • Pressing the left-hand pair of buttons (labeled 5 and 7) activates E-stop. Yes, I know all about why you shouldn’t have E-stop run through software. This is a Sherline mill. Work with me here.
  • The right-hand buttons (labeled 6 and 8) do nothing yet.

The code…

In Sherline.ini:


In custom.hal:

loadusr -W hal_input -KA Dual

All the heavy lifting happens in custom_postgui.hal. As nearly as I can tell, HAL is basically a write-only language, so there’s block diagram of the major chunks of “circuitry” down at the bottom.

First, some setup and the simple buttons:

# Logitech Dual Action joypad

loadrt	and2 count=3
loadrt	conv_s32_float count=3
loadrt	mux2 count=2
loadrt	or2 count=1
loadrt	scale count=4
loadrt	sum2 count=2
loadrt	toggle count=1
loadrt	wcomp count=3

#-- central buttons activate manual mode and restart the program

net 	mode-manual		input.0.btn-base3		halui.mode.manual

net		pgm-resume		input.0.btn-base4		halui.program.resume

#-- left-hand rear buttons active estop

addf	and2.0 servo-thread

net		pgm-estop-0		input.0.btn-base		and2.0.in0
net		pgm-estop-1		input.0.btn-top2		and2.0.in1
net		pgm-estop		and2.0.out				halui.estop.activate

Because the Left Knob and Hat will never be active at the same time, a sum2 block combines the two controls into single value (separate for X and Y, of course). Each sum2 input has a separate gain setting, which is a convenient place to adjust the Y axis sign.

#-- left knob runs XY at variable rate
#   hat runs XY at full throttle

addf	sum2.0 servo-thread

net		x-jog-knob		input.0.abs-x-position		sum2.0.in0
setp	sum2.0.gain0	+1.0
net		x-jog-hat		input.0.abs-hat0x-position	sum2.0.in1
setp	sum2.0.gain1	+1.0
net		x-jog-total		sum2.0.out				halui.jog.0.analog

addf	sum2.1 servo-thread

net		y-jog-knob		input.0.abs-y-position		sum2.1.in0
setp	sum2.1.gain0	-1.0
net		y-jog-hat		input.0.abs-hat0y-position	sum2.1.in1
setp	sum2.1.gain1	-1.0
net		y-jog-total		sum2.1.out				halui.jog.1.analog

The Right Knob values go through scale blocks to adjust the polarity. Note that the Gamepad’s rz axis controls the EMC2 Z axis and Gamepad z controls the EMC2 A axis. Basically, it made more sense to have up-down control Z and left-right control A.

#-- right knob runs Z at variable rate (front-back)
#                   A                 (left-right)

addf	scale.0 servo-thread

net		z-jog-knob		input.0.abs-rz-position		scale.0.in
setp	scale.0.gain	-1

net		z-jog-total		scale.0.out				halui.jog.2.analog

addf	scale.1 servo-thread

net		a-jog-knob		input.0.abs-z-position		scale.1.in
setp	scale.1.gain	+1

net		a-jog-total		scale.1.out				halui.jog.3.analog

There’s only a single halui.jog-speed setting, but the jog speeds for the linear axes and the angular axes differ by so much that Something Had To Be Done. As above, I assumed that only one of the axes would be jogging at any one time, so I could set halui.jog-speed to match the active axis.

A window comparator on each linear axis detects when the joystick is off-center; the output is 1 when the axis is centered and 0 when it’s pushed. Combining those three signals with and2 gates gives a combined linear-inactive signal.

A mux2 block selects the MAX_ANGULAR_VELOCITY from the ini file when linear-inactive = 1 (linear not active) and MAX_LINEAR_VELOCITY when it is 0 (any linear axis off-center).

Done that way, rather than detecting when the angular axis is off-center, means that inadvertently activating the angular axis during a linear jog doesn’t suddenly boost the linear speed. Given that the max linear is about 28 inch/minute and the max angular is 2700 degree/min, it’s a pretty abrupt change.

I’m thinking about adding + shaped gates to at least the Right Knob so I can’t inadvertently activate both Z and A. I’m sure there’s a HAL lashup to do the same thing, though.

#-- set jog speed by toggle from either knob button
#   press any knob button to toggle

addf	and2.1 servo-thread
addf	and2.2 servo-thread
addf	conv-s32-float.0 servo-thread
addf	conv-s32-float.1 servo-thread
addf	conv-s32-float.2 servo-thread
addf	mux2.0 servo-thread
addf	mux2.1 servo-thread
addf	or2.0 servo-thread
addf	scale.2 servo-thread
addf	scale.3 servo-thread
addf	toggle.0 servo-thread
addf	wcomp.0 servo-thread
addf	wcomp.1 servo-thread
addf	wcomp.2 servo-thread

#-- determine if any linear knob axis is active

net		x-jog-count-int	input.0.abs-x-counts	conv-s32-float.0.in
net		x-jog-count-raw	conv-s32-float.0.out	wcomp.0.in
setp	wcomp.0.min		126
setp	wcomp.0.max		128
net		x-jog-inactive	wcomp.0.out				and2.1.in0

net		y-jog-count-int	input.0.abs-y-counts	conv-s32-float.1.in
net		y-jog-count-raw	conv-s32-float.1.out	wcomp.1.in
setp	wcomp.1.min		126
setp	wcomp.1.max		128
net		y-jog-inactive	wcomp.1.out				and2.1.in1

net		xy-active		and2.1.out				and2.2.in0

net		rz-jog-count-int	input.0.abs-rz-counts	conv-s32-float.2.in
net		rz-jog-count-raw	conv-s32-float.2.out	wcomp.2.in
setp	wcomp.2.min		126
setp	wcomp.2.max		128
net		z-jog-inactive	wcomp.2.out				and2.2.in1

#-- convert ini file unit/sec to unit/min and scale for slow jog

net		linear-inactive	and2.2.out				mux2.0.sel

The ini file velocities are in units/second, so a scale block multiplies by 60 to get units/minute.

Another scale block multiplies by 0.05 to get slow-speed jogging. Obviously, that value is a matter of taste: tune for best picture.

Those two values go into a mux2 driven by the output of a toggle triggered by the or2 of the two buttons under the Knobs. Pushing either Knob down flips the toggle.

setp	scale.2.gain	60
net		jog-per-sec		mux2.0.out				scale.2.in
net		jog-per-min		scale.2.out				mux2.1.in0
net		jog-per-min		scale.3.in

setp	scale.3.gain	0.05
net		jog-per-min-slow scale.3.out			mux2.1.in1

net		xy-button		input.0.btn-base5		or2.0.in0
net		za-button		input.0.btn-base6		or2.0.in1
net		xyza-button		or2.0.out				toggle.0.in

net		xyza-slowmode	toggle.0.out			mux2.1.sel

net		axis-jog-speed	mux2.1.out				halui.jog-speed

When the jog speed is at the maximum allowed, it still gets trimmed by the per-axis limits, so you can’t over-rev the motors no matter how hard you try. Even better, changing the values in the ini file automagically affect the gamepad jog speeds.

Now, to make some chips!

The block diagram; click for a bigger image.

HAL Gamepad Block Diagram
HAL Gamepad Block Diagram

7 thoughts on “Logitech Dual Action Gamepad as EMC2 Pendant

  1. Having trouble getting estop to work using your instructions. Everything looks find but the estop doesn’t toggle on the EMC2 interface. You know the big red button in the upper left of the window. I should be seeing it toggle if it working correctly.

    Here’s what in my hal file and everything works except the estop. What am I missing?


    loadrt or2 count=2
    loadrt mux4 count=1
    loadrt and2 count=3

    addf or2.0 servo-thread
    addf or2.1 servo-thread
    addf mux4.0 servo-thread
    addf and2.0 servo-thread

    # set the jog speed for the joypad again use numbers that make sense for your machine
    setp mux4.0.in0 0 # this one must be 0 to prevent motion unless a button is pressed
    setp mux4.0.in1 .25
    setp mux4.0.in2 10
    setp mux4.0.in3 50

    # the following does the magic of setting the jog speeds
    net remote-speed-slow or2.0.in0 input.0.btn-trigger
    net remote-speed-medium or2.1.in0 input.0.btn-thumb
    net remote-speed-fast or2.0.in1 or2.1.in1 input.0.btn-thumb2

    net joy-speed-1 mux4.0.sel0 <= or2.0.out
    net joy-speed-2 mux4.0.sel1 <= or2.1.out
    net joy-speed-final halui.jog-speed <= mux4.0.out

    net joy-x-jog halui.jog.0.analog <= input.0.abs-x-position
    net joy-y-jog halui.jog.1.analog <= input.0.abs-y-position
    net joy-z-jog halui.jog.2.analog <= input.0.abs-rz-position

    #sets the left front controls to toggle estop
    net pgm-estop-0 input.0.btn-base and2.0.in0
    net pgm-estop-1 input.0.btn-top2 and2.0.in1
    net pgm-estop and2.0.out halui.estop.activate

    1. What am I missing?

      Are you pressing the buttons with 5 & 7 embossed in their faces?

      The comment in your code says “left front controls”, but those two buttons are on the left side of the rear panel when you’re holding the front of the gamepad with the cable pointing away from you.

      If that’s not it, then the only way I’ve found to discover obvious blunders is to fire up the Hal monitoring doodad and doggedly verify that things blink on when I expect them to. I must trace every signal from the buttons forward until I find something that isn’t behaving properly, because any assumption I make will eventually turn out to be wrong.

      Then it’s always an obvious blunder like forgetting to ADDF the gate to the servo thread. You’ve got that part right, so it must be something truly humiliating…

  2. Well crap, I figured out that it was working! For some reason I was thinking the code would toggle the estop and now I see it takes both buttons activate it. Duh! The and statement finally dawned on me. I started up the halscope and viewed the traces all the way out to the activate and then noticed the red X Estop was turning off like it was supposed to. Sorry for wasting your time.

    1. Duh!

      That’s the sound of one hand clapping forehead.

      Welcome to the club. There’s a stack of t-shirts right by the Electronics Workbench…

  3. The Left Knob jogs X and Y proportionally to the Knob displacement.

    Does the setup above keep the X and Y axis from moving together when the knob is moved? If you don’t push the knob perfectly centered you’ll get both axis’s moving at the same time.

    1. Does the setup above keep the X and Y axis from moving together

      This one doesn’t have a lockout, but that version does… and it works great!

Comments are closed.