Introduction: Mach4 Pendant

About: I'm retired, living in Queensland Australia with my wife. These are my hobbies, pretty much full time now. Isn't it great to retire and actually pursue your real interests!

Mach4 is a fairly new piece of software by New Fangled Solutions. It is CNC control software.

Because Mach4 is newish there is not too many add-on devices for it.

One very useful add-on device for a CNC machine is a MPG or Pendant. It allows you to control your machine at the machine instead of at the computer keyboard.

There is just nothing available at a reasonable cost and the units that are available need particular motion controllers.

I have a XHC motion controller which is excellent but does not cater for a pendant.

So I decided to make my own pendant.

Luckily Mach4 comes with some plugins, one of which is the keyboard plugin.

The idea to use the keyboard plugin came from a similar instructable here on Instructables but was incomplete and would never work without a lot more research and work; something I have done and present here.

The keyboard plugin allows you to configure shortcuts on the keyboard to perform actions, just like most software.

We can 'send' keystrokes to a Mach4 'Input', have it intercepted by the keyboard plugin and through a PLC script, perform an action. Sounds complicated but once you get your head around how all this works it's fairly simple. I did it one key at a time.

An Arduino can be programed to send keystrokes but not all Arduinos have HID (human Interface Device) capabilities, the Leonardo and the Due being able.

The only shortcoming with this method is it's one way, there is no communication from the computer back to the Pendant, so actual machine coordinates are not available.

If someone knows a way to 'output' axis coords to a HID device via USB please let me know.


*****************************************************************************************************************

TESTING UNCOVERED ERRORS IN THE CODE IN MACH4. (02/January/2020)

SPECIFICALLY THE JOG STEP SIZE SELECTION.

The Pendant works fine and sends the appropriate keystrokes to Mach4, (CTRL1 - CTRL 0).

The Jog Step DRO changes in Mach4 screen but does not actually seem to change the Jog Rate itself.

The errors would be in the PLC script code, Inputs 30 - 39.

I will admit I am stumpted with some of the code in Mach4 and I am unfamiliar with the Lua language.

IF ANYONE OUT THERE CAN OFFER ASSISTANCE WITH THIS PLEASE LEAVE A MESSAGE.

*******************************************************************************************************************

This project is about the design and building of a pendant.

Supplies

Arduino Due - from here

Arduino IDE software to program the DUE. Available free download here. Follow the site instructions for installation.

3.5" Touch screen - here

Rotary pulse generator - here

Prototype board - here

Wirewrap wire - here

4 x 6mm M3 screws, plus nuts

5 x 10mm M3 screws

2 x 100nF capacitors.

0.1" male single row Header pins - here

0.1" double row sockets with long pins - here

0.1" 4 way single row female Header

2-3 mtr USB cable - here

3D printer to make the case.

Small tools and a soldering iron.

NOTE: If you have a different touch screen or a screen that is only 8 bit you may be able to use a Leonardo instead of a Due.

The Arduino firmware code and the wiring will need to be modified to suit.

Step 1: Printing the Case

The case can be printed in one go as the 2 parts will fit side by side on most printers.

I used PLA filament, glass bed with autolevelling, and glue stick adhesive.

Temps were: bed at 65 deg (first layer only) and Hotend at 215 deg

I set my Sli3er at 0.2mm layers and 30% infill.

Everything else can be left at your prefered settings.

My case may look odd as I ran out of filament half way through printing the top. (I printed them seperately) See note below

The holes may need to be drilled out for the screws, I found the holes in the lid just right to accept the screw and make their own threads.

The cutout for the screen may also need to be filed to fit, I did mine while the boards were assembled and screwed into the base so as to ensure alignment.

As a side note, if this ever happens to you there is no need to stop the print.

Cut off any bent end of the filament that is running out, get the new filament prepared by making a clean cut at the end, make sure it's straight for about 30mm.

Push the new filament into the extruder, pushing against the end of the old filament and keep pushing until it is picked up by the sprocket and starts to drive itself.

Step 2: Make the Interface Board

Cut a piece of double sided thru hole plated prototype board to 4'' x 2.1" (yes, imperial I'm sorry).

Mount the single row header connectors as shown in the drawing, the 10 way will need to have its pins bent slightly to align with the Arduino board. I don't know why this connector is not on the standard 0.1" grid but the Arduino designers felt it nessesary for some strange reason.

I find it easier to plug in the Arduino to align the connectors properly, soldering just pins at each end temporarily then unplug the Arduino and solder completely.

The connector for the screen needs to be set off the board by the amount shown in the drawing. Ensure this is level before fully soldering.

Please ignore pin numbering in the connector layout picture (the black background), pin numbering on the CAD drawing is correct.

The rotary connector can be a 4 way if you desire, I made an error here and assigned a 5 way for some stupid reason. This needs to be a 90 degree header. If you don't have, just pull off the black plastic after soldering and bend the pins down. There are 2 x 100nF capacitors across the signals to GND for noise reduction.

Wiring is pretty tricky. I used very thin wire-wrap wire, bared the end about 1mm and just soldered it quickly to the nessesary pin.

Follow the Wiring List provided, take your time and check each connection as you go.

I stripped aboout 35 wires and soldered one end to the Arduino pins first, then cut each to length, stripped and soldered the other end in place. Leave some slack so the wires can be lifted or moved if nessesary.

The end result doesn't look real pretty but I found it impossible to layout as a DIY printed circuit board.

Check each connection against the list with a continuity meter and check each pin against surrounding pins for shorts.

The holes need about 3mm clearance around them.

In the photos of the board it shows a bus-bar for the ground and what should have been the 3.3v but it shows the 5v in connected, don't copy me with the same mistake.

Step 3: Make Rotary Encoder Cable

Make up a cable about 80mm long comprising 4 wires (7x 0.2 cable) soldered to the 4 way female header, strip the other ends about 6mm.

Preferably the colors should be black for GND, red for 3.3v and two signal wires of any color.

The cable shown in the picture came from a stepper motor, I just had it laying around.

Step 4: Assembly

Assembly is pretty straight forward.

Mount the Encoder into the 3 holes provided as shown in the picture with the nuts and washers that come with the encoder.

Plug in the Encoder cable ensuring it is the right way around, connect the other ends in the screw terminals, 0V Black), Vcc (red), B+, B-.

If when testing, the encoder goes the wrong way, swap the two signal wires.

Mount the Interface board into the base and fix with the 4 x 6mm M3 screws and nuts.

Don't use screws any longer than this as they could possibly contact the underside of the Arduino board.

Plug in the Arduino board making sure it is fully seated.

Plug in the screen. Fix a piece of rubber or foam about 6 - 7mm thick and place it under the other end of the screen between the Arduino board to keep them apart. A bit of hotglue could be used here to keep it in place.

Leave the top cover off for now, the Arduino needs to be programmed... next....

Step 5: Programming the Arduino

Copy the Code below, paste into a new text document,save it as "Mach4_Pendant", change the file name extension ".txt" to ".ino". (instructables disallows the upload of software is why we have to go thru this process)

Make a New folder in your Arduino sketch folder (usually 'documents > arduino') with the same name i.e "Mach4_pendant"

copy the file into the newly created folder and double click it to open it in the IDE.

Connect the Arduino board to your PC with a USB cable plugged into the 'programming port'

In the IDE, select 'Tools > Board.

Select Arduino Due (programming port) from the list (NOT the Duemilanove)

In the IDE, select 'Tools > Port

Select the port the Due is connected to, it should say in the dropdown list

Click the 'Upload' button.

You could probably test the screen and functions work at this stage, best NOT connected to your machine.

Copy of the Code:

<p>/* Copyright (c) 2019 Graham Payne<br> *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, 
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
* A Mach4 CNC Pendant.
 It uses the HID capabilities of the Arduino DUE to send key strokes
 to the machine via the PC, over a USB cable.
 Keystrokes are intercepted by the built in keyboard plug-in in Mach4.
 Keystrokes are configured in the plug-in config screen.
 Inputs are then setup to pass the keystrokes to the screen script.
 Some coding is required in the screen editor in Mach4's PLC script to tell the keystrokes what to do.
 The original versionn I made utilised hardware buttons, then a keypad, 2 toggle switches for Enable & Spindle,
 a rotary encoder and a 4 line LCD screen.
 This was done on a Arduino Loenardo (HID capable), used every pin !
 
 Another version had just a touch screen for all functions including jogging. This needed more pins so a DUE was used.
 
 The final version had a touch screen for all buttons but a rotary encoder for jogging, 
 I felt it dangerous to have jogging on the touch screen.
 This is the final version.
 Board Arduino DUE.
 Programming port for programming, Native USB port for connection to Mach4.
 
 This program requires the UTFT libraries and keyboard library.
 It is assumed that the display module is connected to an
 appropriate shield or that you know how to change the pin 
 numbers in the setup.
*/
#include 
#include 
#include "Keyboard.h"
#include  // by Brian Low , <a href="https://github.com/brianlow/Rotary">  https://github.com/brianlow/Rotary

Rota...></p><p>Rotary r = Rotary(20, 21); // make an instance of the rotary encoder UTFT myGLCD(ILI9486,38,39,40,41); // Initialize display URTouch myTouch( 6, 5, 4, 3, 2); // Initialize touchscreen extern uint8_t BigFont[]; // Declare which fonts we will be using int Selected_Axis = 1; //X=1, Y=2, Z=3, A=4 from cycle of button bool Machine_Enabled = LOW; bool Spindle_Enabled = LOW; //float Xpos = 0; //float Ypos = 0; //float Zpos = 0; //float Apos = 0; //int JogStepCounter = 1; //float JogRate = 0;</p><p>void setup() {<br> r.begin(true); // initialise the rotary encoder myGLCD.InitLCD(0); //portrait myGLCD.clrScr(); myTouch.InitTouch(0); // portrait myTouch.setPrecision(PREC_MEDIUM); myGLCD.setFont(BigFont); Send_Key(KEY_F7); // turn spindle off delay(100); Send_Key(KEY_F9); // turn machine off drawButtons(); AxisButtonOn(); // LIGHT UP THE X BUTTON myGLCD.setBackColor(0, 0, 0); // set background to black // JOG_STEP_PRESSED(); // set jog step to 0.01 and screen it } //*********************************************************** // turn on the enable button //*********************************************************** void enable_on() { myGLCD.setColor(255, 0, 0); //red myGLCD.fillRoundRect (1, 1,155, 55); myGLCD.setColor(255, 255, 255); //white myGLCD.drawRoundRect (1, 1,155, 55); myGLCD.setBackColor(255, 0, 0); //red myGLCD.setColor(0, 0, 0); //black myGLCD.print("ENABLED", 25, 21); } //*********************************************************** // turn off the enable button //*********************************************************** void enable_off() { myGLCD.setColor(0, 255, 0); //green myGLCD.fillRoundRect (1, 1,155, 55); myGLCD.setColor(255, 255, 255); //white myGLCD.drawRoundRect (1, 1,155, 55); myGLCD.setBackColor(0, 255, 0); //green myGLCD.setColor(0, 0, 0); //black myGLCD.print("IDLE", 46, 21); } //*********************************************************** // turn on the spindle button //*********************************************************** void spindle_on() { myGLCD.setColor(255, 0, 0); //red myGLCD.fillRoundRect (165, 1, 318, 55); myGLCD.setColor(255, 255, 255); //white myGLCD.drawRoundRect (165, 1, 318, 55); myGLCD.setBackColor(255, 0, 0); //red myGLCD.setColor(0, 0, 0); //black myGLCD.print("SP ON", 200, 21); } //*********************************************************** // turn off the spindle button //*********************************************************** void spindle_off() { myGLCD.setColor(0, 255, 0); //green myGLCD.fillRoundRect (165, 1, 318, 55); myGLCD.setColor(255, 255, 255); //white myGLCD.drawRoundRect (165, 1, 318, 55); myGLCD.setBackColor(0, 255, 0); //green myGLCD.setColor(0, 0, 0); //black myGLCD.print("SP OFF", 190, 21); } //*********************************************************** // from the selected axis highlight the associated button //*********************************************************** void AxisButtonOn() { switch (Selected_Axis) { case 1: myGLCD.setColor(255, 0, 0); //red myGLCD.fillRoundRect (1, 195, 73, 250); myGLCD.setColor(255, 255, 255); //white myGLCD.drawRoundRect (1, 195, 73, 250); myGLCD.setBackColor(255, 0, 0); //red myGLCD.setColor(255, 255, 255); //white myGLCD.print("X",30, 215); break; case 2: myGLCD.setColor(255, 0, 0); myGLCD.fillRoundRect (83, 195, 155, 250); myGLCD.setColor(255, 255, 255); myGLCD.drawRoundRect (83, 195, 155, 250); myGLCD.setBackColor(255, 0, 0); myGLCD.setColor(255, 255, 255); myGLCD.print("Y",113, 215); break; case 3: myGLCD.setColor(255, 0, 0); myGLCD.fillRoundRect (165, 195, 237, 250); myGLCD.setColor(255, 255, 255); myGLCD.drawRoundRect (165, 195, 237, 250); myGLCD.setBackColor(255, 0, 0); myGLCD.setColor(255, 255, 255); myGLCD.print("Z",194, 215); break; case 4: myGLCD.setColor(255, 0, 0); myGLCD.fillRoundRect (247, 195, 319, 250); myGLCD.setColor(255, 255, 255); myGLCD.drawRoundRect (247, 195, 319, 250); myGLCD.setBackColor(255, 0, 0); myGLCD.setColor(255, 255, 255); myGLCD.print("A",277, 215); break; } } //*********************************************************** // turn all the axis buttons off //*********************************************************** void AxisButtonsOff() { myGLCD.setColor(0, 0, 255); //blue myGLCD.fillRoundRect (1, 195, 73, 250); myGLCD.setColor(255, 255, 255); //white myGLCD.drawRoundRect (1, 195, 73, 250); myGLCD.setBackColor(0, 0, 255); //red myGLCD.setColor(255, 255, 255); //white myGLCD.print("X",30, 215); myGLCD.setColor(0, 0, 255); myGLCD.fillRoundRect (83, 195, 155, 250); myGLCD.setColor(255, 255, 255); myGLCD.drawRoundRect (83, 195, 155, 250); myGLCD.setBackColor(0, 0, 255); myGLCD.setColor(255, 255, 255); myGLCD.print("Y",113, 215); myGLCD.setColor(0, 0, 255); myGLCD.fillRoundRect (165, 195, 237, 250); myGLCD.setColor(255, 255, 255); myGLCD.drawRoundRect (165, 195, 237, 250); myGLCD.setBackColor(0, 0, 255); myGLCD.setColor(255, 255, 255); myGLCD.print("Z",194, 215); myGLCD.setColor(0, 0, 255); myGLCD.fillRoundRect (247, 195, 319, 250); myGLCD.setColor(255, 255, 255); myGLCD.drawRoundRect (247, 195, 319, 250); myGLCD.setBackColor(0, 0, 255); myGLCD.setColor(255, 255, 255); myGLCD.print("A",277, 215); } //*********************************************************** // send keystroke function //*********************************************************** void Send_Key(char n) { Keyboard.begin(); Keyboard.press(n); delay(100); Keyboard.releaseAll(); } /* Coords enable = 1, 1, 155, 55 spindle = 165, 1, 318, 55 start = 1, 65, 103, 120 hold = 113, 65, 206, 120 stop = 216, 65, 319, 120 home = 1, 130, 103, 185 goto 0 = 113, 130, 206,185 reset = 216, 130, 319,185

X = 1, 195, 73, 250
Y = 83, 195,155, 250 Z = 165, 195, 237, 250 A = 247, 195, 319, 250 jog step = 0, 260, 155, 315 jog step display box = 165, 260, 319, 315 display box X = 40, 325, 250, 362 display box Y = 40, 364, 250, 401 display box Z = 40, 403, 250, 440 display box A = 40, 442, 250, 479 */ //*********************************************************** // draw the buttons on the screen //*********************************************************** void drawButtons() { enable_off(); // Draw the Enable button spindle_off(); // Draw the Spindle button // Draw the START button myGLCD.setColor(0, 255, 0); //green button myGLCD.fillRoundRect (1, 65, 103, 120); myGLCD.setColor(255, 255, 255); //white border myGLCD.drawRoundRect (1,65, 103, 120); myGLCD.setBackColor(0, 255, 0); //green text background myGLCD.setColor(0, 0, 0); //black text myGLCD.print("START", 13, 85); // Draw the HOLD button myGLCD.setColor(255, 255, 0); //yellow myGLCD.fillRoundRect (113, 65, 206, 120); myGLCD.setColor(255, 255, 255); myGLCD.drawRoundRect (113, 65, 206, 120); myGLCD.setBackColor(255, 255, 0); //yellow myGLCD.setColor(0, 0, 0); //black myGLCD.print("HOLD",128, 85); // Draw the STOP button myGLCD.setColor(255, 0, 0); //red myGLCD.fillRoundRect (216, 65, 319, 120); myGLCD.setColor(255, 255, 255); //white myGLCD.drawRoundRect (216, 65, 319, 120); myGLCD.setBackColor(255, 0, 0); //red myGLCD.setColor(255, 255, 255); //white myGLCD.print("STOP", 234, 85); // Draw the HOME button myGLCD.setColor(128, 239, 239); //viiolet myGLCD.fillRoundRect (1, 130, 103, 185); myGLCD.setColor(255, 255, 255); //white myGLCD.drawRoundRect (1,130,103, 185); myGLCD.setBackColor(128, 239, 239); //violet myGLCD.setColor(255, 255, 255); //white myGLCD.print("HOME", 23, 150); // Draw the GOTO 0 button myGLCD.setColor(230, 230, 230); //silver myGLCD.fillRoundRect (113, 130, 206, 185); myGLCD.setColor(255, 255, 255); //white myGLCD.drawRoundRect (113, 130, 206, 185); myGLCD.setBackColor(230, 230, 230); //silver myGLCD.setColor(0, 0, 0); //black myGLCD.print("GOTO",127, 140); myGLCD.print("0",150, 160); // Draw the RESET button myGLCD.setColor(255, 0, 0); //red myGLCD.fillRoundRect (216, 130, 319, 185); myGLCD.setColor(255, 255, 255); //white myGLCD.drawRoundRect (216, 130, 319, 185); myGLCD.setBackColor(255, 0, 0); //red myGLCD.setColor(255, 255, 255); //white myGLCD.print("RESET",225, 150); AxisButtonsOff(); // draw axis selections X, Y, Z & A off //draw Cycle Jog Step Rate Button // myGLCD.setColor(255,255,0); //red // myGLCD.fillRoundRect (0, 260, 155, 315); // myGLCD.setColor(255, 255, 255); //white // myGLCD.drawRoundRect (0, 260, 155, 315); // myGLCD.setBackColor(255,255,0); //red // myGLCD.setColor(0, 0, 0); //white // myGLCD.print("JOG RATE", 10, 280); // //draw Cycle Jog Step Rate display box // myGLCD.setColor(255,255,255); //white // myGLCD.drawRoundRect (165, 260, 319, 315);</p><p>//draw AXIS display boxes // myGLCD.setColor(255,255,255); //white // myGLCD.drawRoundRect (40, 325, 250, 362); // myGLCD.drawRoundRect (40, 364, 250, 401); // myGLCD.drawRoundRect (40, 403, 250, 440); // myGLCD.drawRoundRect (40, 442, 250, 479); // print axis coordinate place holders // myGLCD.setBackColor(0, 0, 0); //red // myGLCD.setColor(255, 255, 255); //white // myGLCD.print("X:", 1, 335); // myGLCD.print("Y:", 1, 374); // myGLCD.print("Z:", 1, 413); // myGLCD.print("A:", 1, 452); // myGLCD.print(String(Xpos) + " ", 50, 335); // myGLCD.print(String(Ypos) + " ", 50, 374); // myGLCD.print(String(Zpos) + " ", 50, 413); // myGLCD.print(String(Apos) + " ", 50, 452); } //*********************************************************** // flash the button border when pressed in red //*********************************************************** void FlashBorder(int x1, int y1, int x2, int y2) { // Draw a red frame while a button is touched myGLCD.setColor(255, 0, 0); // red myGLCD.drawRoundRect (x1, y1, x2, y2); while (myTouch.dataAvailable()) //while pressed myTouch.read(); myGLCD.setColor(255, 255, 255); // back to white myGLCD.drawRoundRect (x1, y1, x2, y2); } //*********************************************************** // toggle the enable button //*********************************************************** void ENABLED_PRESSED() { FlashBorder(1, 1, 155, 55); if (Machine_Enabled) { Machine_Enabled = LOW; //turn enable OFF Send_Key(KEY_F9); Spindle_Enabled = LOW; //spindle turns off (if on) when enabled off. enable_off(); //enable button green } else if (!Machine_Enabled) { //or turn enable on Machine_Enabled = HIGH; Send_Key(KEY_F10); enable_on(); //enable button red } } //*********************************************************** // toggle the spindle button //*********************************************************** void SPINDLE_PRESSED() { FlashBorder(165, 1, 318, 55); if (Spindle_Enabled) { Spindle_Enabled = LOW; Send_Key(KEY_F7); spindle_off(); } else if (!Spindle_Enabled && Machine_Enabled) { //only turn on if machine is enabled Spindle_Enabled = HIGH; Send_Key(KEY_F8); spindle_on(); } } //*********************************************************** // Start button pressed //*********************************************************** void START_PRESSED() { FlashBorder(1,65,103,120); // flash border Send_Key(KEY_F6); // send the keystroke } //*********************************************************** // Hold button pressed //*********************************************************** void HOLD_PRESSED() { FlashBorder(113,65,206,120); Send_Key(KEY_F5); } //*********************************************************** // Stop button pressed //*********************************************************** void STOP_PRESSED() { FlashBorder(216,65,318,120); Send_Key(KEY_F4); } //*********************************************************** // Home button pressed //*********************************************************** void HOME_PRESSED() { FlashBorder(1,130,103,185); Send_Key(KEY_F1); } //*********************************************************** // Go to 0 button pressed //*********************************************************** void GOTO_0_PRESSED() { FlashBorder(113,130,206,185); Send_Key(KEY_F2); } //*********************************************************** // Reset button pressed //*********************************************************** void RESET_PRESSED() { FlashBorder(216,130,318,185); Send_Key(KEY_F3); } //*********************************************************** // X axis button pressed //*********************************************************** void X_PRESSED() { FlashBorder(1, 195, 73, 250); Selected_Axis = 1; AxisButtonsOff(); AxisButtonOn(); } //*********************************************************** // Y axix button pressed //*********************************************************** void Y_PRESSED() { FlashBorder(83, 195, 155, 250); Selected_Axis = 2; AxisButtonsOff(); AxisButtonOn(); } //*********************************************************** // Z axis button pressed //*********************************************************** void Z_PRESSED() { FlashBorder(165, 195, 237, 250); Selected_Axis = 3; AxisButtonsOff(); AxisButtonOn(); } //*********************************************************** // A axis button pressed //*********************************************************** void A_PRESSED() { FlashBorder(247, 195, 319, 250); Selected_Axis = 4; AxisButtonsOff(); AxisButtonOn(); } //*********************************************************** // Cycle thru Jog Rates //*********************************************************** //void JOG_STEP_PRESSED() { // FlashBorder(0, 260, 155, 315);//239, 260, 319, 315 // JogStepCounter ++; //inc counter // if (JogStepCounter >= 11) JogStepCounter = 1; // no more than 10 // Keyboard.begin(); // switch (JogStepCounter) { // case 1: // myGLCD.print("0.01", 185, 280); // Keyboard.press(KEY_LEFT_CTRL); // Keyboard.press('1'); // JogRate = 0.01; // jog rate set at 0.01 // break; // case 2: // myGLCD.print("0.1 ", 185, 280); // Keyboard.press(KEY_LEFT_CTRL); // Keyboard.press('2'); // JogRate = 0.1; // jog rate set at 0.1 // break; // case 3: // myGLCD.print("1 ", 185, 280); // Keyboard.press(KEY_LEFT_CTRL); // Keyboard.press('3'); // JogRate = 1; // jog rate set at 1 // break; // case 4: // myGLCD.print("5 ", 185, 280); // Keyboard.press(KEY_LEFT_CTRL); // Keyboard.press('4'); // JogRate = 5; // jog rate set at 5 // break; // case 5: // myGLCD.print("10 ", 185, 280); // Keyboard.press(KEY_LEFT_CTRL); // Keyboard.press('5'); // JogRate = 10; // jog rate set at 10 // break; // case 6: // myGLCD.print("20 ", 185, 280); // Keyboard.press(KEY_LEFT_CTRL); // Keyboard.press('6'); // JogRate = 20; // jog rate set at 20 // break; // case 7: // myGLCD.print("50 ", 185, 280); // Keyboard.press(KEY_LEFT_CTRL); // Keyboard.press('7'); // JogRate = 50; // jog rate set at 50 // break; // case 8: // myGLCD.print("100 ", 185, 280); // Keyboard.press(KEY_LEFT_CTRL); // Keyboard.press('8'); // JogRate = 100; // jog rate set at 100 // break; // case 9: // myGLCD.print("150 ", 185, 280); // Keyboard.press(KEY_LEFT_CTRL); // Keyboard.press('9'); // JogRate = 150; // jog rate set at 150 // break; // case 10: // myGLCD.print("200 ", 185, 280); // Keyboard.press(KEY_LEFT_CTRL); // Keyboard.press('0'); // JogRate = 200; // jog rate set at 200 // break; // } // delay(100); // Keyboard.releaseAll(); // } //*********************************************************** // Jog positive direction //*********************************************************** void JOG_PLUS() {</p><p> Keyboard.begin(); switch (Selected_Axis) { // jog by selected axis case 1: Keyboard.press(KEY_LEFT_CTRL); Keyboard.press('x'); // Xpos = Xpos + JogRate; // myGLCD.print(String(Xpos) + " ", 50, 335); //display it break; case 2: Keyboard.press(KEY_LEFT_CTRL); Keyboard.press('y'); // Ypos = Ypos + JogRate; // myGLCD.print(String(Ypos) + " ", 50, 374); break; case 3: Keyboard.press(KEY_LEFT_CTRL); Keyboard.press('z'); // Zpos = Zpos + JogRate; // myGLCD.print(String(Zpos) + " ", 50, 413); break; case 4: Keyboard.press(KEY_LEFT_CTRL); Keyboard.press('a'); // Apos = Apos + JogRate; // myGLCD.print(String(Apos) + " ", 50, 452); break; } delay(100); Keyboard.releaseAll(); } //*********************************************************** // Jog negative direction //*********************************************************** void JOG_MINUS() { Keyboard.begin(); switch (Selected_Axis) { case 1: Keyboard.press(KEY_LEFT_CTRL); Keyboard.press('s'); // Xpos = Xpos - JogRate; // myGLCD.print(String(Xpos) + " ", 50, 335); break; case 2: Keyboard.press(KEY_LEFT_CTRL); Keyboard.press('d'); // Ypos = Ypos - JogRate; // myGLCD.print(String(Ypos) + " ", 50, 374); break; case 3: Keyboard.press(KEY_LEFT_CTRL); Keyboard.press('w'); // Zpos = Zpos - JogRate; // myGLCD.print(String(Zpos) + " ", 50, 413); break; case 4: Keyboard.press(KEY_LEFT_CTRL); Keyboard.press('e'); // Apos = Apos - JogRate; // myGLCD.print(String(Apos) + " ", 50, 452); break; } delay(100); Keyboard.releaseAll(); } //*********************************************************** // check rotary encoder for movement //*********************************************************** void Check_Encoder() { unsigned char result = r.process(); if (result) { if (result == DIR_CW) { JOG_PLUS(); } else if (result == DIR_CCW) { JOG_MINUS(); } else if (result == DIR_NONE) { // do nothing } } } //*********************************************************** // check screen for touch //*********************************************************** void Check_Screen() { if (myTouch.dataAvailable()) { myTouch.read(); int x = myTouch.getX(); int y = myTouch.getY(); if ((y >= 1) && (y <= 54)) { if ((x >= 2) && (x <= 154)) { // ENABLE ENABLED_PRESSED(); } else if ((x >= 166) && (x <= 317)) { //SPINDLE SPINDLE_PRESSED(); } } if ((y >= 66) && (y <= 119)) { if ((x >= 2) && (x <= 102)) { // start START_PRESSED(); } else if ((x >= 114) && (x <= 205)) { // hold HOLD_PRESSED(); } else if ((x >= 217) && (x <= 317)) { // stop STOP_PRESSED(); } } if ((y >= 131) && (y <= 184)) { if ((x >= 2) && (x <= 102)) { // home HOME_PRESSED(); } else if ((x >= 114) && (x <= 205)) { // goto 0 GOTO_0_PRESSED(); } else if ((x >= 217) && (x <= 317)) { // reset RESET_PRESSED(); } } if ((y >= 196) && (y <= 249)) { // 4th row if ((x >= 2) && (x <= 72)) { // X X_PRESSED(); } else if ((x >= 84) && (x <= 154)) { // Y Y_PRESSED(); } else if ((x >= 166) && (x <= 236)) { // Z Z_PRESSED(); } else if ((x >= 248) && (x <= 318)) { // A A_PRESSED(); } } // if ((y >= 261) && (y <= 314)) { // JOG STEP // if ((x >= 1) && (x <= 154)) { // JOG_STEP_PRESSED(); // } // } myGLCD.setBackColor(0, 0, 0); myGLCD.setColor(255, 255, 255); } // data available } void loop() { Check_Encoder(); Check_Screen(); }</p>

Step 6: Configuring the Mach4 Keyboard Plugin

Start Mach4 in the profile you have set to use for your machine.

In the Mach4 menu at the top:

Click Configure > Control > Plugins

make sure 'Keyboard Inputs' is ticked, click OK

Restart Mach4.

In the Mach4 menu at the top:

Click Configure > Plugins > Keyboard Inputs

Insert the shortcuts as shown in the picture above.

Note the 'Key' column just needs the actual key to be pressed, Don't press the "control" key, there is a column for that to be ticked if required.

*********************************************************************************************

With the modified code WITHOUT the Jog Rates, you could leave out the 'JogRate1 thru JogRate0' entries.

*********************************************************************************************

Mach4 needs to be restarted for this configuration to take effect

Restart Mach4.

Ensure your machine is NOT enabled ( the Enable button is flashing Green.)

In the menu at the top click Diagnostic > Keyboard Inputs.

You may also need to 'Enable' the keyboard by clicking the 'keyboard' icon in the PC system tray.

Now when you press the F1 key you will see the box flash Green in the Diagnostics Dialog.

Test all the keys, JogRates 1 to 10 will need the 'Ctrl' key held down.

Close the Diagnostics Dialog box.

When the Mach4 keyboard is enabled you will find difficulty typing anything on your computer, so disable it in the system tray when you have finished testing.

Step 7: Setting Up Mach4 Inputs

In the Mach4 menu a the top click Configure > Control.

Click the 'Input Signals' Tab.

Set Inputs as per the 3 pictures above.

Tick 'Mapping Enabled' where indicated in the pictures.

Under the 'Device' column select 'Keyboard' from the drop down list.

In the 'Input Name' column select from the drop down list.

You can make a note in the 'User Description' column if you wish for your reference.

NOTE: If you already have an input on any inputs that I have selected, use a different input, then it's just a matter of changing the code in the PLC script, to be tackled in the next step.

*********************************************************************************************

With the modified code WITHOUT the Jog Rates, you could leave out the 'JogRate1 thru JogRate10' entries.

These are Input #30 thru #39.
*********************************************************************************************

Click Apply and OK to close the Configurations screens.

Restart Mach4.

Step 8: Writing the Input Handling Code in the PLC Script

Ensure your machine is NOT enabled.

In the menu at the top of the screen:

Click Operator > Edit Screen.

You are now in the screen editing section of Mach4.

To change anything in the Screen script it is first edited in a temporary file then saved and compiled to the Screen script.

To do this go to the very top left in the "Screen Tree Manager"

Click the top item, usually "wx4" or whatever you may have named your own modified screen.

In the Properties Box below, click the middle icon with the lightning bolt, then click to the right of 'PLC Script', then click the small button with 3 dots.

See Picture above.

This will open the ZeroBrane Studio for editing the Lua code.

Scroll all the way to the bottom of the page, there you will see the last statements in this page:

"machStateOld = machState

machWasEnabled = machEnabled;"

Set your cursor a couple of lines above these 2 statements and copy/paste the PLC script I have provided in the below text file.

If you had changed any of the inputs in the previous step then you can edit the script now. It's just a matter of changing the ISIG_INPUT number in the relevant section. Change the commented line (starts with --) too for reference.

When you have done click on the "save" button and close Zero Brane Studio.

On the menu at the top click 'Operator > Edit Screen' to exit the screen editor.

When asked to save, Click on 'Yes' to save the screen.

You are done.

Connect the USB cable to the 'Native' port on the Arduino and connect to our PC.
I tested this by enabling the Simulator plug-in in Mach4 and using a different profile, this is pretty safe.

Don't forget the Keyboard will need to be enabled in the system tray for it to work.

When testing on your actual machine please use caution

I hope this will be of use to you, comments and constructive criticism welcome.