CNC Control from Arduino to Zig

November 10, 2020

Originally my engraver used a Teensy 3.2 and Arudino but now it uses an Stm32 and Zig!

If you haven't been following my engraver retrofit project I picked up an old engraver for $100 that had non-working driver and original controller from the early 90's.

That controller was replaced with some 3D printer stepper motor drivers and some Arduino code running on a Teensy 3.2. While this worked for the most part, debugging issues was pretty difficult since the teensy has no debugger.

I decided to give Zig a try and was quite impressed. With Zig I was able to create a simple event loop and use the "async" feature to create mini tasks / coroutines to nicely split up the code. Also since Zig's std doesn't depend on libc, I was able to use a lot of the data structures (like std.TailQueue) out of the box. Zig's error handling is clutch because if it panic's I can see why right away.

Task Queues

The basic design of the code is to use three queues. A command queue, a processing queue, and an action queue. There's three tasks that are just async functions that each run a loop.

The UART IRQ puts data into a buffer. One task parses the UART data into actions moves them into the processing queue. For example a gcode "G0 X1000 Y1000" is converted into a move action.

Another task pre-processes each action to determine the feedrate based on a look ahead algorithm and the s-curve acceleration profile parameters then places the action in the final action queue.

The final action task pops items from the action queue and executes each one. Eg, enable the spindle, lower the head, then move to X, Y with the pre-computed feed rate.

The event loop interleaves each task using zig's async primatives. Since the real move actions take the longest time, the action task is typicially the most active. It simpliy does an "await" for the move action to complete (determined by checking a flag) and lets the other tasks keep running so the code is very simple and 100% stack based. It uses zero allocations.

Here's a short video showing it doing some cutting.

(Note: The motor is really underpowered for cutting wood it's not meant for doing this)

Since it currently doesn't support interpolation of arcs the curve is sent as a sequence of points. Before the adaptive feed control it would stop at each point leaving marks in the cuts. Now it (somewhat) smoothly flows through the turns but will still properly slow down and stop for abrupt changes in direction.

I still need to implement a USB-CDC driver since the stm32 dev boards don't have flow control over the VCP UART but other than that it works pretty well.

Finally, I want to thank the members of the Zig IRC channel for helping me out and providing insight/suggestions on various parts of the project! Thanks!