MPGs, sometimes called "jog wheels" or "pendants" are a user interface to CNC machines which makes it easier to precisely control. MPG is short for "manual pulse generator" since it allows you to manually generate motor move pulses. I've wanted one ever since I took the knobs off my mill to convert it to CNC. With an MPG you can operate the CNC machine in a very similar way to a manual mill - simply by turning a knob to "directly" move an axis. This is a much better human interface for manually machining as well as for setting up parts in for CNC work as you can go fast or move slowly and precisely.

Moving with Pendant

There are a bunch of USB or bluetooth MPGs on the market, but as best I can tell they are often laggy and relatively expensive for a decent one (~$200). Lag is the last thing I want in this kind of interface so I was was looking for options that interface directly with the CNC hardware. I found a generic MPG on ebay for $70 delivered. It has good positive tactile interfaces for jogging, selecting axis, and selecting scales. This is important so that I don't have to look at the control to use it once I get used to it.

MPG

The only problem with using these generic MPGs is that there's a rats nest of wires coming out the end that you have to interface with:

MPG wires

I figured that these wouldn't be on the market if they didn't play nicely with Mach 4 and the Ethernet Smooth Stepper so I ordered one figuring it'd figure it out once I had it in hand. Ultimately it wasn't too hard to setup though it was not plug and play. Documentation of the software system is extremely sparse and I spent many hours fruitlessly googling until I finally stumbled upon an obscure PDF with the magic incantations required to get it working. This is the guide that I wish existed when I was setting out.

How to make a generic MPG work with Mach 4

I'm going to assume that you've wired the MPG up to your controller and setup the inputs within mach. If you need help with that there are many good tutorials online. The only non-obvious thing to note is that you want the quadrature intputs from the MPG to be configured for "Encoder Aux 0" in the smoothstepper:

Once that is setup you assign it as MPG #0 in the Mach MPG configuration tab:

Now that the boiler plate is done it's time for the magic incantations. This isn't actually that complicated, it's just a matter of knowing what code you need to make this work and where to put it. To make this work you will have to drop some code into into the screen load script. Enter the screen editor and bring up the screen load script as shown below:

At the bottom of the script you will need to paste the code at the end of this post which will handle all of the input from the MPG including selecting axis and handling jogs. If you mapped your pins to different inputs than me you will have to adjust the code accordingly. Once you have the code in the right place, save your changes and the MPG should be working. The only other thing you will want to do is setup the "counts per detent" of your MPG in the mach MPG config so that the axis moves the correct distance for a given rotation of the handwheel. This is extremely setup specific but corresponds to the number of motor counts to move per step of the handwheel. In my specific case 4 motor counts equals 0.001" so that is what I use.

Happy MPG'ing

Lua code below

--------------------------------------
-- MPG Code --
--------------------------------------
SigLib = {
    [mc.ISIG_INPUT10] = function (state)
        RunPendant()
    end,
    [mc.ISIG_INPUT11] = function (state)
        RunPendant()
    end,
    [mc.ISIG_INPUT12] = function (state)
        RunPendant()
    end,
    [mc.ISIG_INPUT13] = function (state)
        RunPendant()
    end,
    [mc.ISIG_INPUT14] = function (state)
        RunPendant()
    end,
    [mc.ISIG_INPUT15] = function (state)
        RunPendant()
    end,
    [mc.ISIG_INPUT16] = function (state)
        RunPendant()
    end,
    [mc.ISIG_INPUT17] = function (state)
        RunPendant()
    end
}
---------------------------------------------------------------
-- Pendant function.
---------------------------------------------------------------
function RunPendant()
    local hSig, rc = mc.mcSignalGetHandle(inst, mc.ISIG_INPUT10) -- Is mapped to Port 2 Pin 4 *X Selection
    local XSelection, rc = mc.mcSignalGetState(hSig)
    local hSig, rc = mc.mcSignalGetHandle(inst, mc.ISIG_INPUT11) -- Is mapped to Port 2 Pin 5 *Y Selection
    local YSelection, rc = mc.mcSignalGetState(hSig)
    local hSig, rc = mc.mcSignalGetHandle(inst, mc.ISIG_INPUT12) -- Is mapped to Port 2 Pin 6 *Z Selection
    local ZSelection, rc = mc.mcSignalGetState(hSig)
    local hSig, rc = mc.mcSignalGetHandle(inst, mc.ISIG_INPUT13) -- Is mapped to Port 2 Pin 7 *A Selection
    local ASelection, rc = mc.mcSignalGetState(hSig)
    local hSig, rc = mc.mcSignalGetHandle(inst, mc.ISIG_INPUT14) -- Is mapped to Port 2 Pin 8 *.001 Selection
    local Step001, rc = mc.mcSignalGetState(hSig)
    local hSig, rc = mc.mcSignalGetHandle(inst, mc.ISIG_INPUT15) -- Is mapped to Port 2 Pin 9 *.010 Selection
    local Step010, rc = mc.mcSignalGetState(hSig)
    local hSig, rc = mc.mcSignalGetHandle(inst, mc.ISIG_INPUT16) -- Is mapped to Port 2 Pin 10 *.100 Selection
    local Step100, rc = mc.mcSignalGetState(hSig)
    local hSig, rc = mc.mcSignalGetHandle(inst, mc.ISIG_INPUT17) -- Is mapped to Port 2 Pin 15 *Estop
    local PenStop, rc = mc.mcSignalGetState(hSig)
    local PenJogOn, rc = mc.mcSignalGetHandle(inst, mc.OSIG_OUTPUT10)-- Is mapped to Port 2 Pin 1 *Jog on LED

    if XSelection == 1 then
        mc.mcMpgSetAxis(inst, 0, 0) --X Axis
        mc.mcCntlSetLastError(inst, "X Selected")
        mc.mcSignalSetState(PenJogOn, 1)
    elseif YSelection == 1 then
        mc.mcMpgSetAxis(inst, 0, 1) --Y Axis
        mc.mcCntlSetLastError(inst, "Y Selected")
        mc.mcSignalSetState(PenJogOn, 1)
    elseif ZSelection == 1 then
        mc.mcMpgSetAxis(inst, 0, 2) --Z Axis
        mc.mcCntlSetLastError(inst, "Z Selected")
        mc.mcSignalSetState(PenJogOn, 1)
    elseif ASelection == 1 then
        mc.mcMpgSetAxis(inst, 0, 3) --A Axis
        mc.mcCntlSetLastError(inst, "A Selected")
        mc.mcSignalSetState(PenJogOn, 1)
    else
        mc.mcMpgSetAxis(inst, 0, -1) --No Axis
        mc.mcCntlSetLastError(inst, "No Axis Selected")
        mc.mcSignalSetState(PenJogOn, 0)
    end

    if Step001 == 1 then
        mc.mcMpgSetInc(inst, 0, .001)
    elseif Step010 == 1 then
        mc.mcMpgSetInc(inst, 0, .010)
    elseif Step100 == 1 then
        mc.mcMpgSetInc(inst, 0, .100)
    end

    if PenStop == 1 then
        mc.mcCntlEStop(inst)
    end
end

Next Post Previous Post