This is meant to be a quick introduction on a way to handle real-time constraints in an Arduino system, iteratively building up control software for a CNC mill.


Our local hackerspace has been offered a CNC mill, and we've had a few days to play with it.

CNC mill: overview

As we received it, the machine didn't have endstops, so we added some, using microswitches and 3D printed cases to hold them in place.

CNC mill: endstops

The stepper driver board is one I've not seen so far:

CNC mill: stepper drivers

It is connected to the computer's parallel port

CNC mill: parallel port

The pinout is fairly simple: the data strobe is used as the stepper enable, and the eight data pins are the step and direction pins for the four supported axes. The four status pins are used to report the endstops back to the computer -- so this would work with really any parallel port.

With the device, we got software to play with, which was running under Windows XP, used direct hardware access to write to the parallel port (with a special driver to work around Windows) and had problems keeping up with the timing of the steppers, unless you'd switch the program to "realtime" mode, at which point the mouse was frozen while the motors were moving.

So we'd rather like to replace the software with something that works, and ideally doesn't require a full PC and an obsolete OS to run.

I still have an Arduino board, so I thought I might put it to good use, so I thought about designing a shield to interface to the parallel port. The ribbon cable I used with the SUB-D connector ends in a standard rectangular connector, and ideally I'd be able to directly connect that.

Because I'd like to keep my timings as exactly as possible, I've made sure that all the "STEP" inputs would be connected to the same port on the Arduino, allowing me to write them simultaneously. The second design goal was to create a single-sided PCB, so I could ideally use the CNC router to cut out the PCB.

Using KiCad, I've created a simple schematic connecting everything in a way that makes sense, and has no crossing wires.

I've left out pin D4, which is the Chip Select line to the SD card, as well, so I can use the SD card later on.

CNC mill: Arduino shield schematic

As a PCB, it looks like this:

CNC mill: Arduino shield PCB design

Obviously, this is a bit of a chicken-and-egg situation here: to route that board, we need the router to work, so I made a few temporary connections using breadboard wires.

CNC mill: temporary connection 1 CNC mill: temporary connection 2

With these connections made, it's ready to be powered up. While the debugger is connected, pin D0 is going to be driven by the serial port, so I swapped it with D4 because we're not using the SD card yet.

First Test

I use avr-gcc directly, but keep to the Arduino standard of defining setup() and loop() functions. First of all, I define constants for the bit positions of the I/O pins. Good style would be to include the port mapping in the constants as well, but in the long run, there will be only one place in the code that needs to know that, and doing so would add quite some overhead.

Then, I set up the initial outputs, and the direction registers. No movement should occur yet.

Source code: 01-setup.c

Going Home

Next up, we'd like to see something moving for instant gratification. One of the safest things we can do is go to the home position, which is reached by moving towards the endstops until they are reached.

To do this, I've inserted code into the loop to check the endstop status, and toggle the step outputs for the axes that are not there yet. To get a delay between toggles, I use the _delay_us function from avr-gcc; you could use the delayMicroseconds function in the Arduino library instead.


Source code: 02-home.c

Introducing Real-Time Constraints

Until here, the hardware was operated from the main program code, which means that as soon as the program is busy somewhere, it would stop operating the motors, and the timing is not entirely predictable either, so the motor speed may be variable.

To combat this, I've moved the motor control to a periodic timer interrupt. The handler writes the new hardware settings immediately, and then calculates the next settings -- this gives a defined delay between the occurence of the interrupt, and the reaction of the system.

As a result, the timings are now very exact, and the motor speed remains within tight boundaries -- this will be required for precise movement.

Source code: 03-interrupt.c

Queuing Commands

Next up, we need a way to execute commands sequentially, and actually move somewhere else than home.

So I've added a 16 position ring buffer of commands, and the interrupt handler executes one and then proceeds to the next. When the queue is empty, nothing happens obviously.

The first implementation has three commands, HOME to go to the endstops, MOVE to go to a position, and SHUTDOWN to shut down the steppers.

We preinitialize the command queue to at least see something, and turn up the speed.

Source code: 04-queue.c