Introduction: CNC Robot Plotter

About: 55+ years in electronics, computers, and teaching ... now retired.

This instructable describes a CNC controlled robot plotter. The robot comprises two stepping motors with a pen-lift mounted midway between the wheels. Rotating the wheels in opposite directions causes the robot to pivot about the pen-tip. Rotating the wheels in the same direction causes the pen to draw a straight line. It has the following range of movements ... forward, reverse, rotate-left, and rotate-right.

In operation the robot rotates towards the next co-ordinate, calculates the number of steps, then moves. To speed things up, the robot is programmed to take the shortest turning angle before moving which means that it often draws while travelling in reverse.

Communication with the robot is via a bluetooth link. The robot accepts both keyboard commands and the g-code output from Inkscape.

If you are "into" watercolour painting then this device is able to transfer your sketch onto paper. Changing the SCALE changes the image size which means that you are not restricted to fixed paper dimensions.

Keep in mind that this robot is not a precision instrument. Having said that the results aren't too bad.

Step 1: Mounting Bracket

The mounting bracket was made from a 60mm strip of 18 gauge aluminium sheet. Aluminium was chosen for the bracket as it is light-weight and easy to work. A 3 mm drill was used for the small holes. Each of the larger holes started life as a 9 mm hole that was enlarged with the help of a "rat-tail" file.

The end-plates for the motors in the above photos are 56 mm x 60 mm spaced 110 mm apart when folded. This gave a Centre-to-centre wheel spacing of 141 mm. The Wheel-diameter for this robot is 65 mm. Record these dimensions as their Ratio (CWR) determines how many steps are needed to rotate the robot through 360 degrees.

If you look closely at the photos you will see a hack-saw cut on each of the wheel "skirts". The "sliver" of metal below each of these saw-cuts has been bent down ever so slightly such that:

  • the platform (bracket top) is level,
  • and the robot barely rocks.

It is important that the pen-lift mechanism is midway between, and in-line with, the wheels. Other than that the robot dimensions are not critical.

The pen-lift comprises a plastic medicine bottle that mounts through the aluminium bracket as shown. Holes are drilled through the lid and bottom for the pencil. The pen-lift disc comprises the end off an empty plastic hook-up-wire spool glued to the brass centre of a radio knob which has been drilled to fit the pencil. A small lead fishing sinker, suitably drilled, has been placed over the pencil to ensure contact with the paper at all times.

The robot is powered from six AA batteries mounted close to the wheels to minimize the load on the third support.

[Tip: Sheet aluminium can be cut without the need for a guillotine or tin-snips (which have a habit of deforming the metal). Heavily "score" both sides of the sheet along the cut-line using a steel ruler and a heavy-duty break-off-blade knife. Now place the score-line over the edge of a table and bend the sheet slightly downwards. Flip the sheet over and repeat. After a few bends the sheet will fracture along the entire length of the score-line leaving a straight edge.]

Step 2: Pen-lift and Shield

I experimented with the original cable-tie and opted instead for a plastic disc glued to the brass centre of a "radio-knob". The brass centre was drilled to fit the pen. The grub-screw allows precise positioning of the pen. The plastic disc was cut from the end of a spool of hook-up wire.

The pen-lift mechanism comprises a small servo that came with my original Arduino kit but any small servo that responds to 1mS and 2mS pulses spaced 20mS apart should work. The robot uses 1mS pulses for pen-up and 2mS pulses for pen-down.

The servo is attached to the medicine bottle with small cable ties. The servo horn lifts the plastic disc, and hence the pen, when a pen-up command is received. When a pen-down command is received the servo horn is well clear of the disc. The weight of the disc and brass fitting ensures that the pen remains in contact with the paper. A lead weight can be slipped over the pencil if you want "heavy" lines.

My entire circuit was constructed on an Arduino prototype shield. Unplug the shield whenever you wish to upload a sketch to your Arduino. Once your sketch has been uploaded remove the USB programming cable then replace the shield.

Battery power is fed to the Arduino via the "Vin" pin when the shield is attached. This allows rapid changes to be made to your software without running into battery and bluetooth conflicts.

Step 3: Circuit

All components are mounted on an arduino proto-shield.

The BJY48 steppers are connected to arduino pins A0..A3 and D8..D11

The pen-lift servo motor is connected to pin D3 which has been programmed to output 1mS (millisecond) and 2mS pulses at 20mS intervals.

The servo and stepping motors are powered from their own 5 volt 1 amp power supply.

The HC-06 bluetooth module is powered from the arduino.

The arduino is powered via the Vin pin.

With the exception of the HC-06 bluetooth module, which has a voltage divider comprising 1K2 and 2K2 ohm resistors to drop the bluetooth RX input voltage to 3.3 volts, all resistors are 560 ohms. The purpose of the 560 ohm resistors is to offer short-circuit protection to the arduino. They also make it easier to wire the shield.

Step 4: Software Design Notes

The .ino code for this project was developed using "codebender" at "Codebender" is a cloud based IDE (integrated development environment) which is free to use, has excellent debugging, and auto-detects your arduino.

The SCALE and CWR constants used in the code are determined by:

  • the robot dimensions,
  • the motor specification,
  • and your choice of "stepping mode".

Motor Specifications

The "28BYJ-48-5V Stepper Motors" used in this project have a "stride angle" of 5.625 degrees / 64 and a "speed variation ratio" of 64/1. This translates to 4096 possible steps for one turn of the output shaft but assumes that you are using a technique called "half-stepping".

How Stepper Motors Work

The "28BYJ-48-5V Stepper Motors" have four coils each with a shaped iron core that contains eight poles. Each of the four pole-pieces are displaced such that there are 32 poles spaced 360/32 = 11.25 degrees apart.

If we energize (step) one coil at a time (wave-stepping), or two coils at a time (full-stepping), the rotor will make one complete revolution in 32 steps. Since the internal gearing is 64/1, one turn of the output shaft requires 2048 steps.


This robot uses half-stepping.

Half-stepping is a technique whereby half-steps are created by alternately energizing a single coil, then two adjacent coils, thus doubling the number of steps from 32 to 64 for one turn of the rotor. This is the equivalent of 64 poles spaced 360/64 = 5.625 degrees apart (stride angle).

Since the internal gearing is 64/1, one turn of the output shaft requires 4096 steps.

The binary patterns for achieving half-stepping are documented in the move(){...} and rotate(){...} functions.


SCALE calibrates the robot's forward and reverse motion.

Assuming a wheel diameter of 65 mm then the robot will move forward (or backward) PI*65/4096 = 0.04985 mm per step. To achieve 1 mm per step (Inkscape uses mm for its' co-ordinates) we must use a SCALE factor of 1/0.04985 = 20.0584. This means that the number of steps needed to travel between any two points is "distance* SCALE".


The CWR (Circle-diameter to Wheel-diameter Ratio) [1] is used to calibrate the robot's turn-angle. A high CWR offers greatest resolution and minimum cumulative error, but the downside is that it will take longer for the robot to turn.

Assuming that the robot wheels are spaced 130mm apart then the wheels must travel PI*130 = 408.4 mm for the robot to rotate 360 degrees. If the diameter of each wheel is 65mm then one turn of a wheel will move the robot PI*65 = 204.2 mm around the circle. For the wheels to travel the full circle distance they must turn 407.4/204.2 = 2.0 (twice).

This translates to a CWR of 2 and a resolution of 360/(CWR*4096) = 0.0439 degrees per step.

For greatest accuracy the SCALE and CWR should both use as many decimal places as possible.


The wheel-tracks form a circle when the robots turns 360 degrees. Since the wheel-tracks overlap the formula for CWR is:

CWR = wheel-spacing/wheel-diameter.

The GCODE Interpreter

The robot only responds to Inkscape commands starting with G00, G01, G02, and G03.

It ignores any F (feedrate) and Z (vertical position) codes as the robot can only travel at one speed, and the pen is always up for code G00 and down for all other codes. The I, and J ("biarc") codes used when plotting curves are also ignored.

The unused code M100 is used for the "MENU" (M for Menu).

Extra T-codes have been added for test purposes (T for Test)

The code for my interpreter was inspired by

Step 5: Installing the Robot Software

Switch-off then unplug the "motor / blue-tooth" shield. This achieves two things:

  • It removes the battery-pack while you program the arduino via your USB cable
  • It removes the HC-06 blue-tooth device as programming is NOT possible while the Blue-tooth module is connected. The reason for this is that you can't have two serial devices connected at the same time.

Copy the contents of "Arduino_CNC_Plotter.ino" into a new arduino sketch and upload it to your arduino. Unplug your USB cable once the software has been uploaded.

Reconnect the above shield ... your robot is "ready to roll".

Step 6: Setting Up Your Bluetooth

Before you can "talk" to the robot the HC-06 bluetooth module must be "paired" with your PC.

If your PC doesn't have blue-tooth then you need to purchase and install a Bluetooth USB dongle. The necessary drivers are contained within the dongle. Just plug it in and follow the on screen instructions.

The following sequence assumes that you are using Microsoft Windows 10.

Left-click "Start | Settings | Devices | Bluetooth". Your screen will display the bluetooth status of each device that can be connected. The bottom-left screen shot shows that the PC is currently aware of some bluetooth earphones.

Turn on the robot. The HC-06 bluetooth module will start flashing and the device will appear in the bluetooth window as shown in the centre-bottom screen shot.

Left-click "Ready to pair | Pair" and enter the password "1234" as shown in the top screen shot.

Left-click "Next" to pair the device. Your screen should now be similar to the lower-right screen shot which says "HC-06 Connected".

Step 7: Installing the Terminal Emulation Software

In order to "talk" to your robot you need a terminal emulation software package the purpose of which is to connect your keyboard to the robot, and send g-code files to the robot, via the bluetooth link.

My choice of terminal emulation software for this project is "Tera Term" as it is highly configurable. The software is free to use and the latest version is available from:

Double-click "teraterm-4.90.exe" from your "Download" folder and follow the on-screen instructions. Select the default settings. Left-click "Serial" then "OK" at the opening screen.

Configuring Teraterm

Before we can "talk" to the robot we must configure "Teraterm":

Step 1:

Left-click "Setup | Terminal" and set the screen values to:

Term size:

  • 160 x 48
  • Un-check the two boxes immediately below


  • Receive: CR+LF
  • Transmit: CR+LF

Leave the rest of the screen with the default values.

Click "OK"

Step 2:

Left-click "Setup | Window" and set the screen values to:

  • Click "Reverse" (changes the screen background colour to white)

Leave the rest of the screen with the default values.

Click "OK"

Step 3:

Left-click "Setup | Font" and set the screen values to:

  • Font: Droid Sans Mono
  • Font style:: Regular
  • Size: 9
  • Script: Western

Click "OK"

Step 4:

Left-click "Setup | Serial" and set the screen values to:

  • Port: COM20
  • Baud rate: 9600
  • Data: 8 bit
  • Parity: none
  • Stop: 1 bit
  • Flow control: none
  • Transmit delay: 100 msec/char, 100 msec/line

Click "OK"

Close the warning screen "Cannot open COM20"


  1. My blue-tooth uses COM20 for blue-tooth send and COM21 for blue-tooth receive. Your blue-tooth port numbers may differ.
  2. The transmit delays are to slow things down when using "File | Send ... ". The arduino seems to miss lines if you try speeding things up. "File | Send ..." seems reliable with the the values shown but feel free too experiment.

Step 5:

Left-click "Setup | Save setup ... " and left-click "Save"

Close Teraterm

Step 6:

Turn on your robot. The blue-tooth LED will start flashing.

Open Teraterm and wait for the message "COM20 - Tera Term VT" to appear in the top-left corner of the Teraterm screen. The blue-tooth LED should now be steady

Type "M100" without the quotes ... a menu should appear. The numbers 19: and 17: that appear on the screen are the Xon and Xoff handshaking codes from the arduino..

Congratulations ... your robot is now configured.

Step 8: Test Charts

The "Menu" contains two test charts.

T103 plots a simple square. All corners should meet. Adjust the CWR constant and recompile your code if they don't.

The theoretical CWR for my design was CWR = 141/65 = 2.169. Unfortunately the corners didn't quite meet. To reduce the calibration time I plotted two squares ... one with a CWR = 2 and the other with a CWR = 2.3. If you study the above photo you will see that ends of one square are "open" while the other ends "overlap". Measure the end-to-end distance for each of the squares and grab a sheet of graph paper. Draw a horizontal line with (in this case) 30 divisions labelled 2.0 through 2.3. Using as large a scale as possible, plot the "overlap" distance above the horizontal line and the "open" distance below the line. Connect these two points with a straight line and read off the CWR value at the point where the diagonal line cuts the CWR axis. For my robot this CWR point was 2.173 ... a difference of 0.004 !!

T104 plots a more complex test chart.

The Inkscape g-codes for this test chart are contained in the file "test_chart.gnc". The "biarc" "I","J" parameters shown in the code have been ignored which accounts for the segmented circle.

Step 9: Creating an Outline

The following procedure uses "Inkscape" and assumes that we wish to draw a flower from an image titled "flower.jpg".

Inkscape version 0.91 comes with gcode extensions and may be downloaded from Click "Downloads" and select the correct version for your computer.

Step 1: Open your image

Open Inkscape and select "File|Open|flower.jpg".

Choose the following options from the pop-up screen:

Image Import Type: ............ Embed

  • Image DPI: ......................... From file
  • Image Rendering Mode: ... None
  • OK

Step 2: Centre the image

Click F1 (or the top-left tool in the sidebar)

Click the image ... arrows will appear

Simultaneously press-and-hold your "ctrl" and "shift" keys then drag a corner-arrow inwards until the page outline appears. Your image is now centred.

Step 3: Scan your image

Select "Path | Trace Bitmap" then choose the following options from the pop-up screen:

  • colors
  • uncheck "stack scans"
  • repeat: update ... scan number ... update
  • click OK when you are happy with the number of scans

Close the pop-up by clicking the X in the top-right corner.

WARNING: Keep the number of scans to an absolute minimum to reduce the robot plotting time. Simple outlines are best.

Step 4: Create an outline

Select "Object | Fill and Stroke|". A pop-up with three menu-tabs will appear.

  • Select "Stroke paint" and click the box beside the X
  • Select "Fill" and click the X

Close the pop-up by clicking the X in the top-right corner. An outline is now superimposed over the image

Deselect your image by clicking outside of the page.

Now click inside the image. A message "Image: 512 x 768: embedded in root", or similar, will appear at the bottom of your screen.

Click "delete". Only the outline remains.

Step 5: Time-out

Time for a little exploring.

Click F2 (or the 2nd from top tool in the sidebar) and move the cursor over the outline. Note how the outline flashes red as the cursor passes over the different paths.

Now click the outline. Notice how a number of "nodes" appear. These "nodes" need to be converted into g-code co-ordinates but before we can do that we need to assign a reference co-ordinate to our page.

Step 6: Assign the page co-ordinates

Press F1 then click the outline.

Select "Layer | Add Layer" and click "Add" in the pop-up window. The g-code extensions that we are about to use require at least one layer ... even if it is blank !

Select "Extensions | Gcodetools | Orientation points". Choose "2-point mode" from the pop-up window and click "Apply".

Dismiss any warning messages.

Click "Close" to close the pop-up

The bottom-left corner of your page has been assigned the co-ordinates "0,0; 0,0; 0,0"

Step 7: Select a tool

Select "Extensions | Gcodetools | Tools library" and click:

  • cone
  • Apply
  • OK .... (to clear the warning)
  • Close

Press F1 and drag the green screen off the page outline.

Step 8: Adjust the tool and feed settings

This step is not required but has been included for completeness as it shows how to change the tool "diameter" and "feed" settings should you have a milling machine.

Click the "A" symbol in the sidebar then change the settings shown in the green screen from:

  • diameter: from 10 to diameter 3
  • feed: from 400 to 200

Step 9: Generate the g-code

Press F1

Select the image

Select "Extensions | Gcodetools | Path to Gcode | Preferences" and change:

  • File: flower.ncg .............................................(numerical control g-code filename)
  • Directory: C:\Users\yourname\Desktop ... (storage location for flower.ncg)
  • Z Safe Height: 10

Without leaving the pop-up window, select the "Path to Gcode" menu tab and click:

  • Apply ... (this may take a long time ... wait !!)
  • OK ....... (dismiss any warnings)
  • Close ... (once the code has been created)

If you examine the outline it now consists of blue arrow-heads (lower image).

Close Inkscape.

Step 10: Verify Your Code is an online program for visualizing the image that your g-code will create. Simply drop your g-code onto the left-hand panel of the simulator and the corresponding visualization will appear on the right-hand side of your screen. The red lines show the tool-path and robot pen-lifts.

The "Path | Trace Bitmap" settings for the top image were:

  • "Colors"
  • "Scans: 8"

The "Path | Trace Bitmap" settings for the bottom image were:

  • "Edge detection"
  • "Threshold: 0.1"

Unless you need the detail always create a simple image.

Step 11: Sending an Inkscape File to the Robot

Let's assume that we want to send a file "Hello_World_0001.ngc" to the robot.

Step 1

Power up the robot.

Place the robot on the bottom-left corner of the drawing-page and point it towards 3 o'clock. This is the default starting position.

Open Teraterm and wait until the bluetooth light stops blinking. This indicates that you have a link.

Step 2

Check that the maximum X and maximum Y values in the file that you are about to send will fit on the page. For example the attached "Hello_World_0001.ngc" shows the maximum X value to be:

G00 X67.802776 Y18.530370

and the maximum Y value to be:

G01 X21.403899 Y45.125018 Z-1.000000

If you want your image to be larger than the above 67.802776 by 45.125018 mm then change the plot-size using the following menu options:


T102 S3.5

This command sequence displays the menu, so that you can see the T-codes, then increases the image size 3.5 times (350%)

Step 2

Left-click "File | Send file ..."

"Browse" for the "Hello_World_0001.ngc" file.

Left-click "Open" . The file will now be sent to the robot line-by-line.

It's that simple ... happy plotting :)


  • All MENU commands MUST be in upper-case.
  • The 19: and 17: shown in the above photo are the arduino handshaking codes (decimal) for "Xoff" and "Xon". The colons were added to improve the visual appearance. An Inkscape command follows each "Xon".
  • You should never see two X,Y co-ordinates in the same line. If this happens, increase the serial delay times from their current value of 100mS per character. Shorter delays may work ...
  • The "Hello World !" plot shows signs of cumulative error. Tweaking the CWR should fix this.

  Click here   to view my other instructables.