Mach 4 hand held control pendant project

I’m starting a new thread for this project to document its progress. This first post is a copy of my project introduction from another thread (which was not project specific)

[quote]The project I’m working on is being done mostly for fun as I realized between working my full time job and running a small business on the side I have not done a fun project that I wanted to do for a LONG time! One of the things I have been working on for a while now both at the university and side business is interfacing with Mach 4. At the university I just finished up getting an old Bridgeport Discover 308 CNC machine refurbished and controlled via a Galil motion card and Mach 4. This machine originally had an 80286 PC in control! Now it has a modern PC motherboard, Galil motion card, Windows 7 and Mach 4. Well, I say ‘finished up’ but I should have said operational. I ‘rediscovered’ a bug in the Galil firmware yesterday that the guys at Mach found previously. Fortunately it just means porting the fix to work with the motion mode I have to use for the card I have.

The user console was kept somewhat stock looking as I wanted to add a pendant for jogging, etc. Rather than buy one I decided it would be more fun to build one and then maybe make it available for others to use and customize for their needs. So for example on certain CNC machines you might want different things displayed on the LCD, or different arrangement of butting or other controls on the pendant. But as with any project getting an LED to blink is an important first step! [/quote]

Below is an image of the CNC mill

I am using a Spider 2 Gadgeteer kit as it came with a nice touch display and it provided a chance to play with the Gadgeteer stuff which I had done very little of before. I laid things out in a ‘Pendant’ like arrangement on the Holey board that come with the kit.

1 Like

I decided to use a serial port on the Spider with a USB Serial module to exchange data with Mach 4. These keeps the original USB client port open for debugging. This could be swapped to do serial debugging and CDC if one wanted. I ordered a USB Serial module (and RS485 Serial module for another project) but did not want to wait for it to arrive so I dug through my junk box and found an old Parallax USB2Ser board (FDTI chip) and an eBlock expansion board. A small perfboard and jumper wires served to glue everything together and I have a ‘Redneck Gadgeteer Serial Module’.

After getting familiar with Glide and setting up a COM port in Gadgeteer parlance I am now able to send simple commands over the serial connection and update the display. I send something like “XDRO=123.4567” and the X axis DRO on the pendant is updated.

Tomorrow I plan to work on updating the ‘SIM’ example code which comes with the Mach 4 SDK (simulated motion controller device) to output the DRO positions. Once that is working we’ll see if we can jog the machine with the joystick.

1 Like

I love the use of the eBlock expansion module. I still have a few of those left in my collection, and they’re still useful for a variety of tasks where a plain-old breakout module would require more wiring.

I am able to receive DRO updates from Mach 4 and update the Glide screen. The rate at which the .NewLine events are generated seems really slow which I’m wondering is a Gadgeteer driver issue. I started a new thread on that subject (link below) to make it easier to find if someone is experiencing a similar problem in the future.

Slow serial thread:
https://www.ghielectronics.com/community/forum/topic?id=22922

Once I get the slow serial issue figured out I’ll see about sending data to Mach 4 from the Spider2. Maybe just a button press at first, then I’ll try to jog with the joystick.

Well the slow serial issue wound up being mainly a slow grey matter issue, i.e. I made a silly assumption about the rate at which the data was being sent. Now that I realized I did indeed need to slow down how often I was writing the Spider2 is not suffering from buffer overflows.

On the Mach4 side of the problem I briefly though about using the Modbus interface as I would have to do nothing but some configuration. That meant making the Spider speak Modbus and would eliminate me learning about Mach4 plug-in development. So today I installed Boost so I would have access to boost:asio and got Mach4 to write to the Spider2 and read back from it. The write/read functions are simple and blocking so they will be replaced by some good async stuff.

For anyone interested in Mach 4 one powerful feature they added are ‘Registers’. A register is like a scratch pad that each device can create its own Register entries. The scripting engine has access to the registers and you can access them from GCode macros as well. It makes it very, very easy to pass data to/from plug-ins to scripts. My idea is to have my plug-in register as two devices: InputDevice, OutputDevice. The user will set up registry entries in Mach4 like: InputDevice->btnST. When you send data from the Spider you do so in a simple text format like: “btnST=1”. The plug-in will parse the string and set InputDevice->btnST to 1. Likewise for the output device “xDRO=123.456” will be sent to the Spider2 which will display “123.456” in the X axis position readout on the LCD.

Well that is the plan at least :slight_smile:

After another week of playing with this on and off things are coming along nicely. I picked up a USBSerial module from GHI and it has replaced my homemade setup. I also got a pulse counting module on the same order so I can try out some sort of encoder as an MPG later on.

After trying different ideas with getting data into and out of Mach 4 it was apparent I was making life hard on myself. There are different types of registers:

[ul]Input and Output are bool and can be mapped as switches, etc[/ul]
[ul]Encoder is a long int and meant to be mapped in as an actual motor/axis encoder or MPG[/ul]
[ul]General are ‘general purpose’ and can’t be automatically mapped[/ul]

By mapping I mean that the Mach 4 plug-in (calling it EZIO for now) takes the data from the Spider2 and writes it into the Input, Encoder registers. Then in Mach 4 you go into the configuration and say ‘I want the jog left signal to come from EZIO Input Register 1’ and then it ‘just works’. In a similar manner you can map Mach 4 outputs to the EZIO Output registers and then the plug-in sends them to the Spider2. Given this built in and easy way to do things in Mach I just created a fixed set of Inputs, Outputs, Encoder and General registers that for the plug-in.

One annoying thing I found yesterday is that the Gadgeteer serial port wrapper does not expose the ‘Error’ event. Typically I have seen buffer overflow type errors when entering a break point on the Spider2 while Mach 4 keeps sending packets, or vice versa. To combat this I now check the size of the TX and RX buffer and just purge them if they are too full.

The simple packet format is (from Mach 4 point of view) :

// Decode the received reply string
// packet format-> :STRING1:STRING2:STRING3:
//               inputs <- ":I0=b:I1=b:I2=b...I7=b:"
//	           encoders <- ":E0=d:E1=d:E2=d:E3=d:"
// (4-7 input)  general <- ":G4=b:G4=b:G4=b:G4=b:"
// if packet does not start/end with ':' error, throw away

So if a packet does not start and end with a ‘:’ it is pitched. The line delimiter is a ‘\n’ but it is stripped off before the decoder is called. The decode method from the Spider2 is below. Some basic checks are done to try and make sure that each ‘word’ in the packet, i.e. “I0=1” makes sense before it is processed. You can see I commented out my initial int.parse and made a separate function to catch errors and tell me if the conversion was OK. Last night I was wondering what happens is I do the String.Split on a word that has no ‘=’ in it? I’ll have to try it and see but perhaps a check before the split to see if an ‘=’ is there. It would still be possible for a device to send non-sense like ‘abc=qwe’ so I’m not sure the best way to check for word ‘sensibility’ in general. (also note that I just changed the Spider2 code yesterday so that the serial Rx thread pushes received packets into a Queue and a timer even in the main thread pops them off the Queue and decodes, updating the appropriate arrays. The code that does this “xPos = machAxisPos[0];” should not be in the decode method. )

I suspect in the end I’ll do a CRC on the packet to try to avoid processing corrupted data, that should be relatively fast and easy for folks to replicate even on different hardware.

        /// <summary>
        /// Deocde RXd data packet, update local values
        /// We read everything as strings
        /// </summary>
        /// <param name="packet"></param>
        /// <returns>True if good packet</returns>
        // Decode Tx packet from Mach 4
        //       axis positions -> ":M0=f:M1=f:M2=f:M3=f:"   -> machAxisPos
        //              outputs -> ":O0=b:O1=b;O2=b...O7=b:" -> machOutputs
        // (0-3 output) general -> ":G0=b:G1=b:G2=b:G3=b:"   -> machGeneral
        private bool decodeRXpacket(string packet)
        {
            bool value = false;

            // error if does not start/end with ':'
            if (packet[0] == ':' && packet[packet.Length - 1] == ':')
            {
                value = true;

                string[] data = packet.Split(':');
                foreach (string word in data)
                {
                    if (word.Length > 3) // a valid word has to have at least 4 characters
                    {
                        string[] nameValue = word.Split('=');
                        char nameType = nameValue[0][0];
                        int nameIndex = 0;//int.Parse(nameValue[0].Substring(1, nameValue[0].Length-1));
                        bool valOK = getInt(nameValue[0].Substring(1, nameValue[0].Length - 1), ref nameIndex);

                        if (valOK)
                        {
                            if (nameType == 'M' && (nameIndex >= 0 && nameIndex < 8))
                            {
                                // handles machAxisPos
                                machAxisPos[nameIndex] = nameValue[1];
                            }
                            else if (nameType == 'O' && (nameIndex >= 0 && nameIndex < 8))
                            {
                                // handles machOutputs
                                machOutputs[nameIndex] = nameValue[1];
                            }
                            else if (nameType == 'G' && (nameIndex >= 0 && nameIndex < 4))
                            {
                                // handles machGeneral
                                machGeneral[nameIndex] = nameValue[1];
                            }

                            xPos = machAxisPos[0];
                            yPos = machAxisPos[1];
                            zPos = machAxisPos[2];
                        }
                    }
                }
            }

            return value;
        }
1 Like