Arduino-EMC Integration: How it Works
One of the things that still sets “real” CNC machines apart from DIY conversions (apart from the thousands of pounds of cast iron, spindles with more torque than a small car, etc.) is the use of dedicated control panels. Bob Warfield’s site has a great page showing a number of panels from manufacturers like Fanuc along with some very impressive homebrew solutions. Besides giving direct access to frequently-used functions, these panels also provide a more natural feel, especially for semi-manual operations, which are especially common in home shops.
Despite their utility, the vast majority of hobbyists seem to get by with mouse and keyboard-based control. There are a variety of commercial solutions available, but they’re generally not cheap. Moreover, interfacing them to the PC tends to be messy. The easiest way to do it is through the parallel port, but you run out of pins really fast. I/O cards can give you a lot more pins to play with, but they add further cost and complexity.
Enter the Arduino open-source microcontroller board. For about $30 you get a powerful, complete, easy-to-program platform with 16 I/O pins and a serial-over-USB interface to any PC. This looked like a perfect solution, and Jeff Epler, one of the main developers behind the Axis GUI, had published a demo project proving that it could be done. But while it got a lot of people excited, it didn’t seem to lead to much else: I don’t recall seeing a single post or email on the list about Arduino-based human-machine interfaces (HMI).
As I installed and ran Jeff’s code, I think I figured out why. It ran perfectly the first time, which is kind of a record for me, and got me excited too because this really proved that it worked. Lots of times people publish code that doesn’t quite compile, run, or function as it’s supposed to, and this was the sort of thing that had the potential to be quite finicky.
But while it ran exactly as described, Jeff’s code was an example of obfuscation through optimization: at every level from the firmware to the serial protocol to the Python-HAL interface, it did everything with such economy of code that it was quite difficult to make sense of, at least to a semi-engineer like myself. So rather than trying to extend Jeff’s code, I ended up starting over from scratch, writing something that is no doubt less efficient, but a lot easier for mere mortals like myself to work with. And the good news so far is that I think it’s efficient enough to get the job done.
The basic principles of the system are easy enough to understand, provided that you’re somewhat comfortable with how EMC configurations actually work:
- HAL Config: HAL provides a virtual breadboard of sorts that lets you map pins and signals to each other. Some of these are physical, i.e. you can wire the E-stop function to be triggered by Pin 10 on the Parallel port, but they can also map a signal (e.g. “turn flood coolant on”) to a virtual pin provided by another piece of software.In this case, I use the hal_postgui config file to wire up all the functions that the Ardunio-powered panel will talk with. If you’ve wired up an e-stop switch, it’s pretty much the same idea.
- Python-HAL User Module: HAL provides a mechanism for users to create their own modules in Python. These modules implement whatever functionality the user wants, and then expose that functionality via standard HAL pins and signals. There’s a Python script, which imports a “hal” library that allows the programmer to create HAL objects and declare the externally-visible inputs and outputs. If you’re a programmer, these can be thought of just like objects and method signatures. If you’re not, just think of it as a way for a custom program to talk with HAL in HAL’s native language.
In this application, the Python module is responsible for managing the serial communication layer on the PC. HAL signals get converted into serial messages which can be sent to the Arduino, while the Arduino’s serial messages get converted into HAL signals. In other words, HAL talks to the Python module and the Python module talks to the Arduino over the serial port, and vice-versa.
- The Arduino is responsible for taking serial messages and converting them to physical output signals, and for taking physical input signals and converting them into serial messages back to the PC.
When you get down to it, there’s virtually nothing going on here but translation from one layer to another, times three, and back the other way. It’s less computer science than computer plumbing, and I found more than a few idiosyncrasies along the way, particularly in getting the serial communications to work as needed.
While I may have seemed to be slagging Jeff’s code earlier for being too hard to understand, another factor is that getting this stuff working, from scratch, requires you to understand each layer (HAL-EMC, the Python user module, serial communication, and Arduino) somewhat well. Going into this I had a moderate amount of experience with the Arduino environment and have written quite a bit of software for a living, but the rest was a mystery to me, and it took a couple dozen sessions of banging my head against every available wall before I got most of it working.
While I’ll be documenting and publishing my source code in more detail, for those immediately interested in trying this out, here’s my cheat sheet for getting into it:
- Get comfortable with the Arduino environment and libraries.
- Get comfortable with basic Python (it’s easy if you’ve used Java, PHP, or Ruby before), and particularly the PySerial library. One of the trickiest parts for me was understanding how to send structured data over the serial line; it’s a VERY low-level protocol compared to anything remotely modern. The best way (I think) is to structure everything as fixed-length messages along the lines of this example. Before you try to talk with EMC, write a Python program that sends and receives messages from the Arduino. Once you’ve got that working, you’re about 80% of the way there.
- Work through the HAL Advanced Tutorial first, then do the Creating Userspace Python Components tutorial.
Once I got the basic pieces working, I began working towards a more complete system, including an encoder for jogging, a 25-key keypad, and a 20×4 LCD. While the Arduino only has 16 usable I/O pins, I was able to fit 25 keys in by using a matrix scanning approach, just as you would with an LED matrix. While you could step up to an Arduino Mega and get a lot more I/O pins, I wanted to keep the initial version cheap and accessible for those who already had an Arduino on hand.
I initially chose to use the LCD partly for the heck of it, as I happened to have one in my parts pile, but also because it created a lot of opportunity for powerful display features. With just 6 digital pins, I got the ability to display darn near anything that could be rendered as text. Part of me would have liked to have physical LEDs right on the keypad for certain things (like designating the active axis for manual jogging), but I’ve found ways to display it on the LCD that I think are pretty good. On the flip side, it’s allowed me to create a remote display of a number of things that I think will be quite useful, especially if your main monitor is not right next to your machine. In the long run, I think this will provide a lot more upside than the regular LEDs would.