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.
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 setp mux2.0.in0 [TRAJ]MAX_LINEAR_VELOCITY setp mux2.0.in1 [TRAJ]MAX_ANGULAR_VELOCITY 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.