How to build an 2-axis Arduino CNC Gcode Interpreter

Computer model of where the 3-axis CNC router will move to cut the shape

Purpose

Building robots is great fun. “Good” robots need to move accurately. Stepper motors are a great way to move accurately – they move a predictable amount and then stay where you put them. To use stepper motors we need a way to easily turn our human desires into machine instructions into stepper movements. This is done with an interpreter. I’m going to show you a simple interpreter written for Arduino and an Adafruit Motor Shield (v1) 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, strings, methods, while(), if(), and switch(). 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 codes for the instructions they wanted to give and wrote programs that ran start to finish in a sequence. Each program probably did one small job each 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 build 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 understand all my patterns and respond immediately.

Our interpreter will work in real-time. 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 a a common 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.

Method

The Arduino will need to:

  • Setup:
    • start listening to the serial connection
    • initialize some stuff
    • tell whoever is listening that we’re ready for more instructions.
  • Loop forever:
    • Wait for a message
    • Read the message
    • Interpret the meaning
    • Do something
    • tell whoever is listening that we’re ready for more instructions.

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 machine will listen for semi-readable gcode commands, interpret them, and then move the motors according to what I told it to do.

If one motor only moves in the X direction and one only moves in the Y direction this will conform nicely to a cartesian coordinate system. I’m also going to use Bresenham’s Line Algorithm to move both motors at once. I can draw lines by moving each motor at different relative speeds. I can draw curves by chopping the curve into lots of tiny lines that approximate the curve shape. I could add a third axis for up and down, or more for rotations. I’d need six for a robot arm or a Stewart Platform.

Gcode

Gcode is used by CNC machines like mills, lathes, 3D printers, and more. 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 (\n) 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
  feedrate(200);  // set default speed
  ready();
}


/**
 * display helpful information
 */
void help() {
  Serial.print(F("MixologyBot "));
  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
  while(Serial.available() > 0) {  // if something is available
    char c=Serial.read();  // get it
    Serial.print(c);  // repeat it back so I know you got the message
    if(sofar<MAX_BUF) buffer[sofar++]=c;  // store it
    if(buffer[sofar-1]==';') break;  // entire message received
  }

  if(sofar>0 && buffer[sofar-1]==';') {
    // we got a message and it ends with a semicolon
    buffer[sofar]=0;  // end the buffer so string functions work right
    Serial.print(F("\r\n"));  // echo a return character for humans
    processCommand();  // do something with the command
    ready();
  }
}

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
    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 moving many things at once when you need them to all finish at the same time and they don’t all move at the same speed.

/**
 * 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&gt;0?1:-1;  // direction to move
  int diry=dy&gt;0?1:-1;
  dx=<a href="/shop/3d-printer-filament">ABS</a>(dx);  // absolute delta
  dy=<a href="/shop/3d-printer-filament">ABS</a>(dy);

  long i;
  long over=0;

  if(dx&gt;dy) {
    for(i=0;i&lt;dx;++i) {
      m1.onestep(dirx);
      over+=dy;
      if(over&gt;=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&lt;dy;++i) {
      m2.onestep(diry);
      over+=dx;
      if(over&gt;=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 &gt; ~16k.
}


/**
 * Set the feedrate (speed motors will move)
 * @input nfr the new speed in steps/second
 */
void feedrate(float nfr) {
  if(fr==nfr) return;  // same as last time?  quit now.

  if(nfr&gt;MAX_FEEDRATE || nfr&lt;MIN_FEEDRATE) {  // don't allow crazy feed rates
    Serial.print(F(&quot;New feedrate must be greater than &quot;));
    Serial.print(MIN_FEEDRATE);
    Serial.print(F(&quot;steps/s and less than &quot;));
    Serial.print(MAX_FEEDRATE);
    Serial.println(F(&quot;steps/s.&quot;));
    return;
  }
  step_delay = 1000000.0/nfr;
  fr=nfr;
}

Source

Want the entire source in one file, ready to compile? Here you go. I use this code in a cocktail mixing robot with great success.

Video!

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

Conclusion

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 3 M commands.

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

Tags: , , , , , , , ,

21 Responses to “How to build an 2-axis Arduino CNC Gcode Interpreter”

  1. Richard Graver says:

    This is really cool..

    I was thinking about something like this, and how cool it would be if someone made it.
    Maybe something like this exists.. but if it doesn’t, I think it should. A gcode generator that could generate top and bottom fill patterns from line art. Basically drawing the line art in one fill pattern and the rest of the fill in another pattern. Maybe using the line algorithm for the dark parts of the line art, and something like hilbertcurve for the remainder, or whatever. When you sand and finish hilbertcurves printed in black filament it looks like some kind of “other worldly” material. I would love to experiment with some of the metallic filaments to see how they look. It would certainly make something like the Puzzle Cube from Hell Raiser look a lot more interesting when printed… or even just a project box for a dev board. Just sharing some random thoughts.

    Great Code!

  2. Chris C says:

    I’m glad someone’s doing this. I’m working on the same darn thing and bought the adafruit motorshield before finding out about grbl and easydrivers.

    What do you use to make the gcode and send it to the arduino?

    • Dan says:

      CamBam has worked for me in the past. I also wrote code for the Makelangelo that converts regular images into giant single line drawings in gcode.

  3. Romeo Saha says:

    Can you give me G-CODE interpreter software for the Arduino Mega 2560 microcontroller. It is mostly programmed in C

    with some specialized functions proper to the arduino. The idea is to use the FIRMATA protocol, so that the arduino can

    communicate serially with an android device or a computer. This g-code interpreter is going to be used in a small home made

    cnc machine, were it will manipulate some stepper motors. This g-code interpreter will need to do some of the following

    things:

    Recieve packets with multiple lines, so that they can be decoded at the same time, instead of doing it one-by-one

    (requesting one by one)

    Interpret the g-code commands, so that the stepper motors can be moved throughout linear interpolation or circular

    interpolation.

    Move between 3 and 5 stepepr motors. The output for each motor is only the step signal and the dirrection. I am using the

    microstepping driver A3977. The initial milestone is to move 3 stepper motors, but I would like to add a 4th and 5th

    stepper motor to improve the abilities of the machine.

    Calculate its actual point in space, relative to a home position.

    Calculate acceleration and decceleration for each stepper motor, so that the machine stops at the exact point it should.

    Monitor several sensors like, gas, sound, temp, tilt, vibration, etc…..it would be great if all the info about these

    sensors could be seen on the computer or the android device.

    Controll a working tool. With the option of adding more tools, like a spindle, extruder, laser..etc…these will be added

    in future developments.

    It would be great if the program was buildt in a way that allows for future improvements and additions.

    • Dan says:

      It sound like you want our GCodeCNCDemo – https://github.com/MarginallyClever/GcodeCNCDemo. The 6AxisRumbaTimerInterrupt version will drive up to 6 stepper motors at once and accept GCODE commands. It can listen and drive at the same time, meaning no delay between commands. The RUMBA is based on a Mega 2560 microcontroller.

      If you would like to add trapezoidal motion control, predictive motion optimization, A3977 support, or more, please do! Download the code, write a version, and make a pull request. We’ll add it into the project along with credits for your work so it can be shared with everyone.

  4. Jatin Batra says:

    Can you help with H-bridge as drivers ?
    I would like to make a 4 axis stepper bipolar controller with H Bridges as the stepper driver.
    I am pretty sure this is achievable but no sources are there over web.

  5. Michael Wernsing says:

    awesome job!!
    maybe is a silly question, but there is a java program similar to DrawbotGUI.jar to send the commands to GcodeCNCDemo?
    Thanks

    • Dan says:

      Thank you! It’s not a silly question. Several people have asked for a java program that could send gcode to any serial device. It is one of the many things on my very long list of todos.

  6. Hi Dan,
    Thanks for this.
    If I wanted to make my own drivers rather than use Ada Fruits, what part of the code would I change?

    • Dan says:

      That all depends on what controller you’re going to use. Try removing the adafruit library and hitting compile. Everywhere that suddenly doesn’t work must be a spot you have to change.

      • Thanks Dan! Just read the response. I went ahead and got the Ada Fruit boards. Really like them. Code is easy to follow. I’m not totally clear as to where you place the G-Code but I’ll read up a bit more.

        • Dan says:

          The program teaches the arduino to listen and obey gcode commands. It listens to the serial connection. You can use a program like the gcodesender to deliver lots and lots of gcode or you can send one line at a time over through the Arduino serial window.

          • Hey Dan!
            I got it up and running and it runs beautifully. Great job.
            I was wondering how I would substitute a regular DC motor, or on and off into the code.
            Basically, travelling between one spot to another, the motor is off, so that way there is no drilling.

          • I’m thinking that I want to use G-Code commands such as
            M101 Extruder on, fwd M102 Extruder on, reverse M103 Extruder off

          • Dan says:

            I think it might be easier to use G00 E[distance]. you can use -distance to reverse the extruder. I believe this is the command format used by slicing programs. If you use this you can piggy back on their work and not have to write your own slicing software. A more useful change would be separate feed rates for each axis, so that some axies can move at different speeds than others.

  7. Nathan Camp says:

    Dan,

    Thank you for your work on this project. I was trying to use it as a starting point for a project I was working on. I’m having a little trouble getting your code to work right. I’m using GcodeCNCDemo2AxisV1 due to the motor shield. The first command works fine. If I give a G01 X100 Y100; both steppers move as they should. All other commands work fine if they are the first command after a reset, but no commands work after the first command. For example, if I give the following set of commands:

    G01 X100 Y100; //Moves like it should
    M18; //Just echoes M18; but does not release the drive.
    G01 X200 Y-100; //Steppers just sit there and do not move.

    Each command works (Sent from the serial monitor) if it is the first command that is issued after power up or a reset. But all following commands are ignored.

    I looked in some of the other versions and the communication and parsing routines looked to be the same. I put some debugging code in parsenumber and process command, and they run, but no information is getting parsed (on commands after the first, the always return a -1).

    I was wondering if you had seen this and if I needed to look at another version to get this running.

    Thanks

  8. Nathan Camp says:

    Dan,

    I found my answer. I’m on 1.0.5 of the Arduino software, and the Serial monitor was sending a newline and carriage return after the commands. The serial buffer did not finish clearing and had two left over characters in it after the command was processed. That messed up the next commands since they did not start is G or M (they started newline).

    I set the monitor to No Line Ending, and everything worked fine. Thanks again for a fine project.

  9. Rich says:

    Hi Dan,

    Thanks for this, it has been a great help to me whilst developing my own 3 axis CNC.

    I’m currently at a stage where the option to do arcs G02 / G03 commands would be very useful, however all my attempts so far have been unsuccessful, therefore I was wondering if you have any tips in relation to achieving arcs using the adafruit motor controller V2.

    Thanks,
    Rich

    • Dan says:

      G02 and G03 can be approximated by breaking arcs down into lots of short, straight lines. I’ve always done it with the added restriction that all arcs have a constant Z height. Some systems use another gcode command to define the normal of the plane that contains the arc. If you have the arc center, start, end, direction, and plane normal then you can drive your machine in any 3D arc. Ill try to put a demo together.

Leave a Reply