CNC Auxiliary Input Board

After a little mishap involving my CNC machine I have been slowly restoring it to working order with a few pitfalls on the way. This post describes a small Arduino compatible auxiliary input board I designed around a ATmega8 microcontroller.


I recently replaced the main control board on my CNC machine with a generic 3 axis controller (referred to as a 'chinese blue board' in most LinuxCNC references). It seems that when the old control board burnt out the inputs on the parallel port were damaged, I have ordered a replacement PCI card but it did lead me to consider alternative methods of getting inputs into LinuxCNC. A quick Google search turned up this page which shows how to pass inputs from an Arduino into the LinuxCNC HAL (Hardware Abstraction Layer) model.

Blue Board Controller

It seems that LinuxCNC provides support for userspace programs to communicate with the HAL subsystem (and even provide a nice Python library to simplify the process). With these tools it is easy to monitor an auxiliary control board over a serial port and feed events to and from the HAL.

Given this it seemed like a good opportunity to separate some of the monitoring functions from the control board on to a separate, isolated unit. When my old control board failed the additional inputs failed as well so the E-Stop button failed to work which is really not acceptable. Having a separate board handling the inputs that is electrically isolated from the main power rails to the motors and spindle makes things a fair bit safer.

Some of the features I wanted included:

  • A wider range of input options. Both my old control board and the new one are limited to 4 digital inputs - enough for axis limit switches and either E-Stop or a probe input. I would like to provide some analog monitoring as well - specifically current and voltage levels on the two main power rails.
  • Better input protection. My CNC machine seems to build up a fair amount of static electricity during heavy use and this can cause problems when using the probe.
  • An E-Stop that really stops the machine. As I described in my earlier post pushing the E-Stop button when things got out of control had no effect. I want E-Stop to actually cut the power to the motors regardless of what state LinuxCNC is in.

The version of the board I describe here is the first prototype - it has enough features to prove that the method works but does not yet include any of the more advanced features I have planned. It should give me enough functionality to use the CNC machine to mill the final board design.

Board Design

The board is very simple - the CPU at the heart of it is an ATmega8 running on the internal RC oscillator. I loaded the optiboot bootloader for compatibility with the Arduino IDE. The firmware for the CPU is written with the Arduino libraries to make development as simple as possible.

Input Protection Circuitry

The main role of this version of the board is sampling binary inputs. I am using the circuit shown above to drive the pins of the ATmega through a transistor rather than direct connections. I do get some static build up on the CNC bed so an additional diode is in place to stop any stray voltages from leaking into the VCC line.

I have added some LEDs to the circuit for notifications (probe closed, communications activity, etc). These are also driven indirectly through a transistor operating as a low side switch.

Because this project is pretty small and very task specific I haven't put the code and design up in GitHub as with previous projects. If anyone is interested in accessing those documents please drop me a line and I will make a repository for it.


The communications protocol I am using is very simple, the binary input state is simply encoded as a 8 character binary value (eg: '00010010') and sent out on the serial port. The entire state (all input signal values) is sent whenever an input changes or more than a second has passed since the last update was sent.

To determine if there is someone on the other end listening the board expects the ASCII exclamation mark character as an acknowledgment. Rather than requiring an explicit acknowledgment for every packet I simply check for at least one acknowledgment received after two packets have been sent. This is not 100% reliable but is enough to indicate that the data coming from the board is being read by something. One of the indicator lights on the board shows the current communication state so it is easy to tell at a glance if everything is hooked up properly.

HAL Integration

Integrating with the LinuxCNC HAL is both simple and complicated at the same time. Writing the code to register inputs and then update their state is fairly painless, integrating that with the rest of LinuxCNC (so it knows that our probe signal should indicate the end of a G38.2 move for example) is somewhat fiddly.

To get the data into LinuxCNC requires writing a custom HAL module. There is full support for Python including a 'hal' module you can import so this is relatively straight forward.

# Bring in the HAL layer
  import hal
  print "ERROR: Failed to import HAL module"

The first thing your program has to do is register itself with the HAL, add definitions for the inputs and/or outputs it provides and then signify that it is ready to run.

  model = hal.component("cncmon")
  model.newpin("probe", hal.HAL_BIT, hal.HAL_OUT)
  model.newpin("probe-invert", hal.HAL_BIT, hal.HAL_OUT)

I provide two versions of each signal - the value as read and an inverted version. This comes in handy when the signals are mapped to LinuxCNC functions - some of them (such as E-Stop) may require inverted values to what the board provides.

The string provided to the 'component' constructor gives the name of the module. This is used as a prefix for the signal names to identify them so our 'probe' signal becomes 'cncmon.probe'.

The rest of the program is an endless loop that updates the pin values. The component instance can be treated like a dictionary so this is fairly straight forward.

# Names of the signals (from MSB to LSB, None for unused)
NAMES = (None, None, None, "zlimit", "ylimit", "xlimit", "estop", "probe")

def processLine(line, model):
  """ Process the line received from the module.
  values = list()
  for x in range(len(names)):
    if line[x] == '0':
    elif line[x] == '1':
      # Invalid character, discard input
      return False
  # At this point we have everything, update the model
  for x in range(len(names)):
    if names[x] is not None:
      model[names[x]] = values[x]
      model[names[x] + "-invert"] = not values[x]
  return True

# Main loop
  while True:
    line = input.readline().strip()
    if len(line) == 8:
      if processLine(line, model):
except KeyboardInterrupt:
  raise SystemExit

Before integrating the module with the LinuxCNC application it is best to test it in isolation. This can be done with the 'halrun' command without the LinuxCNC application running.

Note: LinuxCNC seems to use the X11 shared memory extension so you can't run 'halrun' over SSH. This also means you can't run the UI on a standalone VNC session (I tend to use 'tightvncserver' to set up remotely accessable standalone X sessions to connect to - it doesn't work with LinuxCNC).

For LinuxCNC to be able to load your module it needs to be executable and on the path. I simply made a softlink from '${HOME}/bin/cncmon' to the actual location of the '' file. All you need to do is to tell the HAL to load your module and then inspect the pin definitions and values to make sure everything is registered properly. The command sequence looks like the one below:

$ halrun
halcmd: loadusr cncmon
halcmd: show pin

halcmd: show pin

If all goes well you can now integrate the module into your configuration so it will be loaded and active when you start the application.

Loading and Using the Module

Having the module load automatically requires adding some extra lines to your HAL configuration file for your setup. LinuxCNC places all the configuration information in the directory:


My default configuration is called 3020T so my HAL file is at ${HOME}/linuxcnc/configs/3020T/3020T.hal. The additional lines needed involve loading the module and then connecting pins defined by the module to the standard pins required by LinuxCNC. To active the E-Stop and Probe inputs I added the following:

loadusr -W cncmon
net estop-ext <= cncmon.estop-invert
net probe-in <= cncmon.probe
net probe-in => motion.probe-input

The loadusr command is the same command used when testing the module with halrun. The -W option tells LinuxCNC to wait until the module has initialised itself before proceeding.

The remaining lines connect the inputs from the board to the appropriate pins looked at by LinuxCNC. To be frank I don't fully understand how it all works yet - I was able to cobble together a working configuration by referring to the documentation and copying some other example configurations while replacing references to parallel port pins numbers with my module pin names.

Next Steps

At the moment the board is about as simple as can be - all it does is monitor binary inputs and send the status across to LinuxCNC. There are still some issues with the board in it's current design that I need to resolve though.

As the inputs are not coming through GPIO pin on the processor there is a fair bit of latency between the input being triggered and LinuxCNC being informed of the event. For the limit switches this is not such a big issue - they can be positioned such that the extra travel that would occur before LinuxCNC gets the event and stops the movement would not cause any physical damage to the machine.

The E-Stop is also easily handled - my final version of the board will use the E-Stop input to cut power to all motors prior to sending the event on anyway so any delay in receiving the event at the LinuxCNC end is irrelevant.

The biggest concern (for me at least) is the probe input. Not only is there a fair amount of latency between the probe making contact and LinuxCNC being aware of it but the duration of the delay is non-deterministic. Probes done with this board will not be as accurate as those done with the probe connected directly to the parallel port.

In a lot of cases (general bed levelling for example) this is not a huge issue and it can be mitigated somewhat by using a low probe feed rate. I am in the process of running a series of probe sequences over the bed to get an idea of just how much of an error range is being introduced.

Finally there seems to be a problem with communications stability - the 'active' LED on the board flickers a fair bit which indicates it is not receiving the acknowledgment signals it expects. This could be due to a but in my firmware code or the HAL module takes a lot longer to respond to and dispatch an input event than I expected. I need to do some more analysis to resolve that problem as well.

Input Board Active

On top of those issues there are the remainder of my initial goals to implement. One of the most important is an E-Stop mechanism that will cut power to all motors when activated. At the moment I'm considering using a relay on the main 240VAC line.

I also want to monitor the voltage and current going to the motors, it would be nice to detect over-current situations and optionally use them to trigger an emergency stop. This will hopefully make the machine a bit safer to use and avoid potential damage to the mechanical components.

I am happy with my progress so far though. Pending some validation tests I'm running at the moment I believe I have the system up to state where I can start using it to build the rest of the components I need to finish off the job.