Purpose

3D printers, laser cutters, water jet cutters, robot arms, delta robots, stewart platforms, the Makelangelo: all of these are examples of computer numerical control (CNC) machines. CNC machines need to move accurately and on command. Stepper motors are a great way to move accurately – they move a predictable amount and then stay where you put them. To command the stepper motors we need a way to easily turn our human desires into machine instructions into stepper motor steps. In effect we need our robot brain to be an interpreter. I’m going to show you a simple interpreter written for Arduino that lets you move stepper motors for your robots.

Audience

This tutorial is meant for people who have an Arduino and understand the basics of programming in C. That means variables, methods, while(), if(), switch(), and manipulating strings. There’s even a few ?: style if statements.

Interpreters

In the very beginnings of computers the programmers talked to the machines in machine languages – the ones and zeros of binary. They memorized the binary machine language and wrote programs in that machine language. Each program probably did one small job because they were so hard to write.

Then somebody got smart and wrote the first interpreter – a binary program that could turn a more human language (assembly) into binary. Every language since then has been a different flavor of the same ice cream: trying to find easier ways of letting humans tell machines what to do. Most programs today are written using interpreters that were built using interpreters that were built using the original interpreters. Ours will be, too. So Meta!

Goal

I can’t build something without a clearly defined goal. I need to see the target in order to hit it.

I could write a 10-line program that does one pattern of motor movements but then – just like the binary programmers of yore – I would have to write a new program for every pattern. Compiling, uploading, and testing a program is a time-consuming process. My goal is to write an interpreter that can listen and respond in real time to any pattern.

Before you can run an Arduino program you have to compile and upload it. When our program is done you won’t have to compile anything. You will send the gcode to the Arduino through the serial connection and the Arduino will listen, understand, and obey.

Hardware

My NEMA 17 stepper motors are controlled by an L293 motor shield riding on top of an Arduino UNO. On the motor shield I attached a female power plug so I could easily plug in a 12v power supply. I put a piece of tape on the motor shaft of each stepper so I can easily see it moving. Later I can replace that with pulleys and belts.

[products skus=”MOTO-0003,ELEC-0007,KIT-0002,ELEC-0004″]

For the sake of simplicity I’m going to assume that our motors are moving orthogonal to each other. That means on a grid that one motor moves along the X axis and one moves on the Y axis, also known as a cartesian coordinate system. (Delta robots, stewart platforms, and robot arms all use much more complicated systems to arrive at the same effect.)

Because we’re moving in a grid I can use Bresenham’s Line Algorithm to move each motor at same time with different relative speeds. That means in our cartesian system I can draw lines from anywhere to anywhere and they’ll be nice and straight. I can draw curves by chopping the curve into lots of tiny lines that approximate the curve shape.

Method

The Arduino will need to:

Setup: start listening to the serial connection set up the stepper motors Tell whoever is listening that we’re ready for more instructions.

Loop forever: Wait for a message Read the message Interpret the meaning Act on that meaning Tell whoever is listening that we’re ready for more instructions.



Gcode

The messages I send to the Arduino could be in any language. I could even make up a language! To keep life simple I’m going to use a language other people know and understand, called gcode.

The rules of gcode – the punctuation, syntax, grammar, and vocabulary – are very easy to explain to a machine. Gcode commands all consist of an uppercase letter followed by a number. Here are the codes I’m going to build into the interpreter.

Command Meaning G00 [X(number)] [Y(number)] [F(number)]

G01 [X(number)] [Y(number)] [F(number)] Absolute mode: Move in a line to (X,Y) at speed F

Relative Mode: Move (X,Y) amount at speed F G04 P(number) Do nothing for P seconds G90 absolute mode G91 relative mode G92 [X(number)] [Y(number)] change logical position M18 turn off power to motors M100 print out instructions for the human M114 report position and feedrate

Every (number) is assumed to be a float – a number that might have a decimal place and an exponent. 2.015e-5 is a float.

Anything in [brackets] is optional.

G and M commands cannot be combined in a single instruction.

Arduino software has a nice way to send messages to the PCB through the serial interface window. It’s the magnifying glass on the right hand side of the Arduino window. Unfortunately the serial interface window doesn’t send the return key (

) to the PCB. Instead of return I’m going to use semicolon (;) to tell the machine “this is the end of an instruction”.

Setup

#define BAUD (57600) // How fast is the Arduino talking? #define MAX_BUF (64) // What is the longest message Arduino can store? char buffer[MAX_BUF]; // where we store the message until we get a ';' int sofar; // how much is in the buffer /** * First thing this machine does on startup. Runs only once. */ void setup() { Serial.begin(BAUD); // open coms help(); // say hello set_feedrate(200); // set default speed ready(); } /** * display helpful information */ void help() { Serial.print(F("CNC Robot ")); Serial.println(VERSION); Serial.println(F("Commands:")); Serial.println(F("G00 [X(steps)] [Y(steps)] [F(feedrate)]; - linear move")); Serial.println(F("G01 [X(steps)] [Y(steps)] [F(feedrate)]; - linear move")); Serial.println(F("G04 P[seconds]; - delay")); Serial.println(F("G90; - absolute mode")); Serial.println(F("G91; - relative mode")); Serial.println(F("G92 [X(steps)] [Y(steps)]; - change logical position")); Serial.println(F("M18; - disable motors")); Serial.println(F("M100; - this help message")); Serial.println(F("M114; - report position and feedrate")); } /** * prepares the input buffer to receive a new message and * tells the serial connected device it is ready for more. */ void ready() { sofar=0; // clear input buffer Serial.print(F("> ")); // signal ready to receive input }

The only mystery here should be F() , a special Arduino-only macro. It tells the compiler to put the string in program memory instead of RAM, which can sometimes be the difference between a program that fits on an Arduino and a program that doesn’t.

Loop

/** * After setup() this machine will repeat loop() forever. */ void loop() { // listen for commands if( Serial.available() ) { // if something is available char c = Serial.read(); // get it Serial.print(c); // optional: repeat back what I got for debugging // store the byte as long as there's room in the buffer. // if the buffer is full some data might get lost if(sofar < MAX_BUF) buffer[sofar++]=c; // if we got a return character (

) the message is done. if(c=='

') { Serial.print(F("\r

")); // optional: send back a return for debugging // strings must end with a \0. buffer[sofar]=0; processCommand(); // do something with the command ready(); } } }

I tend to write my comments first, then I write the code between the comments. That way I get my ideas organized and the code is better documented when I’m done.

Interpreting Commands and Responding

/** * Read the input buffer and find any recognized commands. One G or M command per line. */ void processCommand() { // look for commands that start with 'G' int cmd=parsenumber('G',-1); switch(cmd) { case 0: // move in a line case 1: // move in a line set_feedrate(parsenumber('F',fr)); line( parsenumber('X',(mode_abs?px:0)) + (mode_abs?0:px), parsenumber('Y',(mode_abs?py:0)) + (mode_abs?0:py) ); break; // case 2: // clockwise arc // case 3: // counter-clockwise arc case 4: pause(parsenumber('P',0)*1000); break; // wait a while case 90: mode_abs=1; break; // absolute mode case 91: mode_abs=0; break; // relative mode case 92: // set logical position position( parsenumber('X',0), parsenumber('Y',0) ); break; default: break; } // look for commands that start with 'M' cmd=parsenumber('M',-1); switch(cmd) { case 18: // turns off power to steppers (releases the grip) m1.release(); m2.release(); break; case 100: help(); break; case 114: where(); break; // prints px, py, fr, and mode. default: break; } // if the string has no G or M commands it will get here and the Arduino will silently ignore it }

parsenumber(key,default) searches for the letter ‘key’ in buffer. If it finds key it return the number that follows immediately after. If it doesn’t find key it returns ‘default’.

Drawing lines

I first learned about Bresenham’s line algorithm from André LaMothe in one of his books back in the early 90’s. I think it was “The Black Art of 3D Game Programming”? It’s supposed to be used for drawing graphics on a computer screen. It works just as well for our purposes, and it can be extended to any number of motors all moving at once.

The slope of a line can be expressed as dx/dy. Let’s pretend for a moment that dx/dy is 1/3. Three steps on Y equals one step on X. Bresenham’s algorithm starts stepping along Y and adds dx to a counter. when the counter >= Y, step once on X. Here’s the best part: by using the slope of the entire line we can do all the math with whole numbers, which means we shouldn’t get any rounding errors and our Arduino can do the math extra fast. Whole number math nearly always faster than floating point math.

/** * Uses Bresenham's line algorithm to move both motors * @input newx the destination x position * @input newy the destination y position **/ void line(float newx,float newy) { long dx=newx-px; // distance to move (delta) long dy=newy-py; int dirx=dx > 0?1:-1; // direction to move int diry=dy > 0?1:-1; dx=abs(dx); // absolute delta dy=abs(dy); long i; long over=0; if(dx > dy) { for(i=0;i < dx;++i) { m1.onestep(dirx); over+=dy; if(over>=dx) { over-=dx; m2.onestep(diry); } pause(step_delay); // step_delay is a global connected to feed rate. // test limits and/or e-stop here } } else { for(i=0;i < dy;++i) { m2.onestep(diry); over+=dx; if(over>=dy) { over-=dy; m1.onestep(dirx); } pause(step_delay); // step_delay is a global connected to feed rate. // test limits and/or e-stop here } } // update the logical position. We don't just = newx because // px + dx * dirx == newx could be false by a tiny margin and we don't want rounding errors. px+= dx*dirx; py+= dy*diry; } /** * delay for the appropriate number of microseconds * @input ms how many milliseconds to wait */ void pause(long ms) { delay(ms/1000); delayMicroseconds(ms%1000); // delayMicroseconds doesn't work for values > ~16k. } /** * Set the feedrate (speed motors will move) * @input nfr the new speed in steps/second */ void set_feedrate(float nfr) { if(fr==nfr) return; // same as last time? quit now. if(nfr > MAX_FEEDRATE || nfr < MIN_FEEDRATE) { // don't allow crazy feed rates Serial.print(F("New feedrate must be greater than ")); Serial.print(MIN_FEEDRATE); Serial.print(F("steps/s and less than ")); Serial.print(MAX_FEEDRATE); Serial.println(F("steps/s.")); return; } step_delay = 1000000.0/nfr; fr=nfr; }

Source

Want the entire source in one file, ready to compile? Here you go. I use a version of this code in nearly all Marginally Clever robots.

Edit 2014-09-29: I’ve since added examples for Adafruit Motor Shield v1, Adafruit Motor Shield v2, RUMBA, and RAMPs.

Video!

This is an updated version that drives 4 steppers at once. It could be done with even more.

Now you try

Add a parsenumber('N') for line numbers. Then your machine can detect if it receives lines in the wrong order, or request specific lines from the computer. You may also need to understand M110 N[number] so the computer can set the line number.

for line numbers. Then your machine can detect if it receives lines in the wrong order, or request specific lines from the computer. You may also need to understand M110 N[number] so the computer can set the line number. Add a ‘;’ on the end of every command, followed by a checksum. Then the arduino can tell if the message is correctly received.

Add G02 and G03 for curved lines.

Final thoughts

So there you have it. In 293 lines of code we’ve built a really simple CNC machine Gcode interpreter that handles six G commands and three M commands.

If you’d like me to go into more detail about how to make arcs, or something else then please make a comment below and I’ll post about it.

Please share videos & pictures of your creations to the forums.

See Also

How to choose your controller electronics to build your first CNC.