About: PLC, Arduino - Do it yourself project

I had one Dell printer that stops working and I disassembled it into small components. This printer head frame has 2 DC motors and 2 optical encoders which are still in good condition & can be reused to build XY axis for the CNC plotter.

Today, I'd like to share how to build 3 axis CNC plotter from this printer, as well as, how we can control a DC motor plus its optical encoder by P.I.D through 2 signals: STEP & DIRECTION. In this project, DC motors can be simulated as same as stepper motors and we can control them via GRBL firmware for CNC application.

I was inspired by cswiger on his GitHub: He had a good idea to turn step/direction signals into DC servo motor position control.

Let's get started.

Update on October 10, 2020 at STEP 15: Better pictures with hatch fill extension.


Main components:


        Step 2: CIRCUIT DIAGRAM

        In my circuit diagram, the X and Y stepper motor driver are not plugged on the Arduino CNC shield. The STEP and DIR signals of both X and Y axis on CNC Shield are used to control 2 x DC motors.

        The schematic with PDF high resolution is HERE.


        Details of printer components are described below:

        1. DC MOTORS: There're 2 DC motors for X & Y axis as follows:

        • X axis DC motor: RS-455PA-17150

        • Y axis DC motor: RS-385SH-14180

        Through my searches on internet, perhaps these motors belong to the MABUCHI MOTOR manufacturer. But I could not find out the specifications of these motors on MABUCHI MOTOR website according to the order number written on the motors body. I only saw two DC motors equivalent to my printer motors, as follows:

        2. OPTICAL ENCODERS: There’re 2 kinds of optical encoders in DELL printer as follows:

        • X axis linear optical encoder

        - Optical sensor and control board: It is not clear, part number maybe J15 (0947).

        - Encoder strip: H-06/1PM326727.

        • Y axis rotary optical encoder:

        - Rotary disc: 1782CPR/300LPI (B-12).

        - Optical sensor: H30 (0942).

        - PCB board nameplate: 94V-O/ KY033H/ BJ4500F01CP4-1.


        • In the case of rotary encoders, resolution is specified as the cycles per revolution (CPR), some manufacturers use terms like “counts per revolution” (also abbreviated CPR) or pulses per revolution (PPR).
        • Lines per inch (LPI) is a measurement of printing resolution. High LPI indicates greater detail and sharpness.


        The optical encoder is widely used due to its low cost and ability to provide signals that can be easily interpreted to provide motion related information such as speed or position. The two output channels from an encoder, with one being offset by 90 electrical degrees, or one quarter of a cycle that usually called quadrature encoder.

        With a single output encoder, it has no way of detecting in which direction the motion is happening. But for quadrature encoder, it produce two channels, named channel A and channel B. When it moves/ rotates in a forward/ clockwise direction, channel A leads channel B, and in case it moves/ rotates in backward/ counterclockwise direction channel B leads channel A. We can increase encoder’s resolution by counting the rising and falling edges of two channels, detail is explained below.

        1. X1 ENCODING

        X1 encoding: the rising or the falling edge of channel A is counted. When channel B is leading, the movement is considered as counterclockwise or backward, and the count number is decreased.

        2. X2 ENCODING
        X2 encoding: both the rising and falling edges of channel A are counted. When X2 encoding is used, it increases encoder resolution by two times.

        3. X4 ENCODING

        X4 encoding: both the rising and falling edges of channels A and B are counted. When X4 encoding is used, it increases encoder resolution by four times.

        Picture below summarizes the quadrature function of optical encoder:

        For example, we consider a 1782CPR rotary optical encoder - Y axes of Dell printer as described in previous step:

        • X1 – if we count the rising edge of each Channel A pulse, we’ll get 1 pulse per cycle or 1782 pulses per revolution (1782PPR).
        • X2 – if we count each rising edge and each falling edge of Channel A, we’ll get 2 pulses per cycle, or 1782 x 2 = 3564 pulses per revolution (3564PPR).
        • X4 – if we count each rising edge and falling edge of both Channel A and Channel B, we’ll get 4 pulses per cycle, or 1782 x 4 = 7128 pulses per revolution (7128PPR).


        I have found a website that is very helpful in identifying the pins of optical encoder from the printer:

        I couldn’t find part number, manufacturer or data sheets on these optical encoders. In this case, I used a multimeter to measure the resistance among optical sensor pins (6 pins in my case) to identify its pinouts: VCC, GND, channel A, channel B. As mentioned in previous step, there are 2 kinds of optical encoders in DELL printer: X axis linear optical encoder and Y axis rotary optical encoder.


        There are many connectors / headers on the PCB board which confused me and I could not find the encoder pinout diagram in these headers to get its feedback correctly. Finally, I decided to do my own control circuit for this optical sensor as following steps:

        • Firstly, I measured the resistance among the optical sensor pins (6 pins) and recorded them on a table.
        • Secondly, based on my knowledge about the IR LED and Photo-transistor, I figured out its connection schematic and double checked whether I had any mistakes. With the resistor values measured, I guessed this linear optical sensor operating at 5V voltage level.
        • Thirdly, I removed the optical sensor from the PCB control board. Note that this optical sensor is very easy to be broken if we hold the soldering tip on it too long time.
        • Fourthly, for safety, I supplied 3.3V voltage level for powering this linear optical encoder. In my own schematic, I used 2 x 10K resistors connected in parallel because I did not have 5K resistors in my hand.


        There is 4 pins - header on the PCB control board and I easily found the encoder pinout diagram on this header. I kept this original PCB control circuit, just removed 4 pins - header and replaced by 4 cables.

        Header pinout detail is as follows:

        • PIN 1 – GND.
        • PIN 2 – CHANNEL A.
        • PIN 3 – VCC (3.3V).
        • PIN 4 – CHANNEL B.

        Step 6: THE P.I.D CONTROLLER

        The PID controller is a closed-loop controller which is widely used in electrical, automated, and electronic systems. The goal of PID controller is to adjust the control value at the OUPUT by continuously evaluating the ERROR (e(t) = (SP - PV)) between a SETPOINT (SP) and the PROCESS VARIABLE (PV) being controlled and applies a correction based on proportional, integral, and derivative terms, to achieve the stability and rapid response in the system. PID algorithm for DC motor plus optical encoder is described in the diagram below:

        You can read one of the best blog articles about PID algorithm at website:

        In my project, Arduino Mega 2560 is used just like a DC servo controller. It performs P.I.D control for the X and Y axis DC motors.

        Usually, a motor will be driven by speed or position but with this PID controller, the setpoint are STEP plus DIRECTION signals from Arduino Uno R3 which has GRBL firmware pre-installed.

        The PID control signals are as follows:

        • SETPOINT - SP: They are X.STEP/ X.DIR plus Y.STEP/ Y.DIR signals that are sent from Arduino Uno R3 with a CNC Shield to Arduino Mega 2560. Note that Arduinno Uno has GRBL firmware pre-installed.
        • PROCESS VARIABLE - PV: The measured feedback value from quadrature optical encoders to Arduino Mega 2560.
        • OUTPUT: The PWM signals from Arduino L293D Motor Shield (controlled by Arduino Mega 2560) to printer DC motors.


        1. Arduino L293D Motor Shield Overview:

        This motor driver expansion board is based on the L293D chip which is designed to drive up to 4 bidirectional DC motors with individual 8-bit speed selection. It can also drive 2 unipolar or bipolar stepper motors. It contains 4 H-bridges which provide up to 0.6 A per bridge (1.2A peak) at voltages from 4.5 V to 36 V. This shield has pull down resistors to keep the motors disabled during power up. It also features a 2-pin terminal block to ensure separate logic and motor external power supplies. This motor driver shield is capable of driving:

        • Four DC motors and two servos
        • Two DC motors, stepper motor, and two-way servo
        • Two stepper motors and servos

        The following pins are in use on the L293D Motor Shield:

        • Digital pin 11: DC Motor #1 / Stepper #1 (activation/speed control).
        • Digital pin 3: DC Motor #2 / Stepper #1 (activation/speed control).
        • Digital pin 5: DC Motor #3 / Stepper #2 (activation/speed control).
        • Digital pin 6: DC Motor #4 / Stepper #2 (activation/speed control).
        • Digital pin 4, 7, 8 and 12 are used to drive the DC/Stepper motors via the 74HC595 serial-to-parallel latch.
        • Digital pin 9: Servo #1 control.
        • Digital pin 10: Servo #2 control.

        2. CNC Shield V3

        The Arduino CNC Shield makes it easy to get your CNC projects up. It uses opensource firmware on Arduino to control 4 stepper motors using 4 x Stepper Motor Driver.
        Before using this CNC shield with Arduino Uno R3, a control firmware “GRBL” need to be downloaded into Arduino board.



        1. Adapter Shield:

        To do Adapter Shield, I cut one PCB prototype board size 60x90mm and soldered the wires connections following the circuit on STEP 2. Adapter Shield is used for connecting the Arduino Mega 2560 to the L293D Motor Shield and some male headers such as: 4 pins - headers for encoders (Vcc, GND, Channel A, Channel B), X.STEP, Y.STEP, X.DIR, Y.DIR are also soldered on it.

        • Top view of Adapter Shield:

        • Bottom view of Adapter Shield:

        2. Control board assembly:

        • Plug CNC Shield on Arduino Uno:

        • Two copper pillars were installed at the bottom of Arduino Uno board.

        • Connect Arduino Uno to L293D Motor Shield by 2 copper pillars above.

        • Plug L293D Motor Shield on Adaper Shield and finally plug Adapter Shield to Aduino Mega 2560. I've got a neatly organized controller.


        • The underside of the printer head frame is not flat, it has convex and concave supports. I had to use an insulation sheet and cut some long grooves corresponding to the convex supports so that the printer head frame could be fixed horizontally and vertically.

        • For Z axis, I used a DVD player which is very compact, called "HP Super Multi DVD Rewriter" as shown below:

        • I mounted this DVD stepper motor frame on the printhead.

        • I soldered a cable to Y axis - rotary optical encoder (See the detail how to dertimine its pinouts at STEP 5).

        • X axis - linear optical encoder cable was soldered and fixed along the printer frame. (See the detail how to determine its pinouts at STEP 5).

        • My DIY linear encoder control board is hidden inside the printhead, I forgot to take pictures of it while I soldered the circuit. I cannot disassemble this printer frame again because it can cause the linear encoder strip damaged. On my previous disassembled, the encoder strip lost some black lines on it.


        • Arduino Mega 2560 and Arduino Uno connect together by 4 signals: X.STEP, Y.STEP, X.DIR, Y.DIR to control 2 DC motors, in which the Arduino Mega 2560 acts as a DC servo controller and Arduino Uno plus CNC Shield sends the control commands from its GRBL firmware.
        • DC Motors and Optical Encoders: Two DC motors are connected to L293D Motor Shield at M1 & M2 terminals. And their optical encoders are connected to 4pins - headers on Adapter Shield.

        • I plugged stepper motor driver A4988 on CNC shield for Z axis and connected cable from DVD stepper motor to A4988 driver.

        • Mounting flexible coupling to the DVD frame and put the pen into the flexible coupling. To clamp the pen, we can tighten small screws on the flexible coupling. It looked like this, after my assemblies and connections were completed.


        Step 10: ARDUINO CODE

        The 3 axis CNC plotter code is available at my GitHub. Thanks to cswiger for inspiring me.

        In my code, Arduino Mega 2560 pins usage are shown in table below. Take note that digital pin D3 (Interrupt 0) is used for L293D Motor Shield.

        Encoders have 2 signals, which must be connected to 2 pins. There are three options when we use "Encoder" library.

        • Best Performance: Both signals connect to interrupt pins.
        • Good Performance: First signal connects to an interrupt pin, second to a non-interrupt pin.
        • Low Performance: Both signals connect to non-interrupt pins.

        I used the second option and it was declared in program as follows:

        // Set up pins for the Quadrature Encoder
        #define EncoderX_ChannelA   18  // Interrupt 5
        #define EncoderX_ChannelB   22
        #define EncoderY_ChannelA   20  // Interrupt 3
        #define EncoderY_ChannelB   24
        // Encoders
        Encoder XEncoder(EncoderX_ChannelA, EncoderX_ChannelB);
        Encoder YEncoder(EncoderY_ChannelA, EncoderY_ChannelB);

        With "AFMotor" library, when used with the L293D Motor Shield, the AF_DCMotor class provides speed and direction control for up to 4 DC motors. Here below is the constructor for X and Y axis DC motors

        AF_DCMotor motorX(1, MOTOR12_8KHZ);
        AF_DCMotor motorY(2, MOTOR12_8KHZ);

        To simulate a DC motor plus its encoder as same as a stepper motor, we used interrupts to detect rising edge at STEP pulse and combine with DIR signal to determine motor direction and position.

        #define STEP_XPIN           19  // Interrupt 4
        #define STEP_YPIN           21  // Interrupt 2
        #define DIR_XPIN            23
        #define DIR_YPIN            25
        // Simulate DC motor as same as stepper motor
        attachInterrupt(4, doXstep, RISING);  // PIN 19 (Interrupt 4) - Interrupt X step at rising edge pulses
        attachInterrupt(2, doYstep, RISING);  // PIN 21 (Interrupt 2) - Interrupt Y step at rising edge pulses
        void doXstep() 
          if ( digitalRead(DIR_XPIN) == HIGH ) SETPOINT_X--;
          else SETPOINT_X++;
        void doYstep()
          if ( digitalRead(DIR_YPIN) == HIGH ) SETPOINT_Y--;
          else SETPOINT_Y++;

        PID controllers for X/Y axis are created and linked to the specified Input, Output, and Setpoint:

        // PID 

        The parameters of the PID controllers are described below:

        • SETPOINT_X/ SETPOINT_Y: The STEP and DIR signals are sent from GRBL CNC Shield of Arduino Uno to Arduino Mega 2560. Arduino Mega 2560 receive these STEP and DIR signals of each axis X/Y, then combine them to create the SETPOINT values for each PID controller.
        • INPUT_X/ INPUT_Y: They are feedback signals which are read from quadrature encoders of X/ Y DC motors.
        • OUTPUT_X/ OUTPUT_Y: They are PWM output signals which control the X/Y motors.
        • K_P/ K_I/ K_D: They're tuning parameters. These affect how the PID will change the output.

        Step 11: GRBL CALIBRATION

        GRBL parameters for my printer are as follows:

        $010.000Step pulse time
        $125.000Step idle delay
        $20.000Step pulse invert
        $33.000Step direction invert
        $40.000Invert step enable pin
        $50.000Invert limit pins
        $60.000Invert probe pin
        $101.000Status report options
        $110.010Junction deviation
        $120.002Arc tolerance


        Report in inches


        Soft limits enable


        Hard limits enable


        Homing cycle enable


        Homing direction invert
        $2425.000Homing locate feed rate
        $25500.000Homing search seek rate
        $26250.000Homing switch de-bounce delay
        $271.000Homing switch pull-off distance
        $301000.000Maximum spindle speed
        $310.000Minimum spindle speed
        $320.000Laser-mode enable
        $10024.500X-axis travel resolution
        $101192.000Y-axis travel resolution
        $10253.333Z-axis travel resolution
        $11020000.000X-axis maximum rate
        $11120000.000Y-axis maximum rate
        $1122000.000Z-axis maximum rate
        $12050.000X-axis acceleration
        $12120.000Y-axis acceleration
        $12250.000Z-axis acceleration
        $130210.000X-axis maximum travel
        $131297.000Y-axis maximum travel
        $13240.000Z-axis maximum travel

        The important parameters which I have done the calibrations are highlighted in table above.

        1. STEP/MM setting:

        • Z AXIS - $102:

        The step/mm setting for Z axis stepper motor is shown in table below by formula:

        Steps/mm = (Steps per Revolution)*(Micro-steps) / (mm per Revolution)

        The working length of the screw:40.00mm
        Step angel:18°
        The number of steps required for DVD stepper to make 1 complete revolution:20step/rev
        A4988 micro-steps setting:8-
        DVD stepper screw pitch (mm/revolution):3.0mm/rev
        • Y AXIS - $101

        If we're using belts and pulleys, the XY steps/mm can be calculated based on the motor, pulley, and belt specifications but in my case, the printer need to be disassembled. I did it once and don't want to do it again because it can cause my printer damaged.

        Y axis have a 1782CPR rotary optical encoder. When X4 encoding is used, it increases encoder resolution by four times: 1782 x 4 = 7128 pulses per revolution (7128PPR). For setting step/mm of Y axis, I did following steps:

        - Firstly, I connected Universal Gcode Sender program to Arduino Uno (with GRBL), set $101 = 7128 in tab "Firmware Setting". The purpose is I want to check when I command Y axis to move 1mm, the Arduino Mega 2560 will generate PMW to move the DC motor and count 7128 pulses feedback from optical encoder.

        - Secondly, I opened Arduino IDE serial port for Arduino Mega 2560 and turn on debug mode for Y axis in program to monitor all parameters.

        - Thirdly, I marked the home position of Y axis.

        - Fourthly, I used the "Machine Control" tab in Universal Gcode Sender, instruct the printer Y axis to move 1 millimeters. Checked:

        • Is it rotate 1 revolution?
        • Is count number from encoder in serial monitor around the value of 7128?

        - Fifthly, I made a measurement of the true movement from home position: 37.59mm.

        - Sixthly, I applied this formula to calculated the new step/mm = (old step/mm) x (1mm/ real measured value)=192.0 step/mm then update to $101 GRBL.

        - Eighthly, I reset the Arduino Uno (with GRBL), marked the home location and instructed some more commands to move Y axis with difference position. I made a measurement of the true movement and repeated the above step. Finally, step/mm for X was determined to be: 192.0.

        • X AXIS- $100

        - For X axis linear optical encoder, firstly I counted the number of black lines per 1mm on the encoder strip. In my case, it is between 6 and 7 black lines per 1mm. When X4 encoding is used, it increases encoder resolution by four times so step/mm could be between 24 to 28 .

        - Same as Y axis, I marked the X axis home location and instruct some commands to move X axis with difference position then made a measurement of the true movement. Finally, step/mm for X axis is: 24.5


        • To speed up the printer, both X and Y axis maximum rate ($110 and $111) are set to 20000.
        • The Y-axis acceleration ($121) is set to 20 if we set it at a big value then when the motor switches from forward to backward and vice versa its belt will slip and there is a loud noise.


        • X-axis maximum travel ($130): A4 size width 210 mm.
        • Y-axis maximum travel ($131): A4 size length 297 mm.
        • Z-axis maximum travel ($132): DVD working length 40 mm.

        Step 12: P.I.D TUNNING

        GRBL and P.I.D parameters fine-tuning are the most important and the most difficult works of this project. They affect to the printer operation, for example when I draw a line in the X and Y axes with 20mm length, the printer did it precisely, but when I draw a 20mm diameter circle, it got distorted.

        That means, in order for the printer to work correctly, the X and Y axes both have to work precisely when separated each other, as well as, ensure their synchronization when combined together. These calibrations are not easy for both PID and GRBL parameters. It is time consuming and I wished I had a oscilloscope in my hand to figure out all things quickly. I finally found out the PID optimal parameters which are in accordance to the GRBL setting values in the previous step. With these values, my plotter has worked very well.

        // The PID parameters
        double KP_X = 20.0;   // P for X motor
        double KI_X = 0.03;   // I for X motor
        double KD_X = 0.01;   // D for X motor
        double KP_Y = 9.0;    // P for Y motor
        double KI_Y = 0.02;   // I for Y motor
        double KD_Y = 0.01;   // D for Y motor

        In industrial field, the PID controller is usually integrated with a "deadband" function. The goal is saving maintaince cost, it prevent our equipment, for example big valves, oscillating all the time around the setpoint by small worthless movements and cause equipment wear and tear or other overheat issues.

        The deadband represents the range at which the PID controller will allow the process variable (PV) to deviate from setpoint (SP) without applying any correction. For example, imagine that we are trying to maintain the printer's Y-axis at 100mm from home position. When the Y axis moves to the set point, if its position was to fluctuate between 99.9mm and 100.1mm, the PID controller would exert no correction. The dead band or acceptable error would be 0.1mm .

        #define STEPSPERMM_X      24.5    // STEP/mm ($100) is used in the GRBL for DC motor X axis.
        #define DEADBW_X          4.5     // Deadband width in pulses = 4.5 --> Acceptable error for positioning in mm: 0.18mm.
        #define STEPSPERMM_Y      192.0   // STEP/mm ($101) is used in the GRBL for DC motor Y axis.
        #define DEADBW_Y          19.2    // Deadband width in pulses = 19.2 --> Acceptable error for positioning in mm: 0.1mm.

        When we apply the deadband in PID controller, it will increase the service life of the motor because it will stop when its process values are in deadband window. Otherwise, the printer DC motor is always oscillating around the setpoint by PID regulators and it can be getting hot or overheat.

        ERROR_X = (INPUT_X - SETPOINT_X);  
        if (abs(ERROR_X) < DEADBW_X) // If the motor is in position within the deadband width (acceptable error)
            motorX.setSpeed(0); // Turn off the motor
            motorX.setSpeed(abs(int(OUTPUT_X))); // Motor is regulated by PID controller ouput

        We can turn on the debugging mode in Arduino program and check the PID parameters on the serial monitor:

        As we can see, the error between the X axis setpoint (735) and actual counting feedback from encoder (731) is 4, in this case X-axis motor stops because it is within the deadband range (4.5).

        Step 13: TESTING


        • For testing, I used a thin ruler to creat plotting surface of Y axis. Take note that the distance from the center of printer roller to the pen tip is as short as possible, it is about 20 mm in my case.

        • Finally, I added the A4 paper to printer. It's ready to do testing.

        • We should do the PID and GRBL final calibrations and fine-tunings in this step when the printer is working with load. It can work smoothly when we do a no load test but it can shake crazily when paper is loaded. Making a printer acts like a CNC plotter is really not easy!!!

        2. INKSCAPE

        - From the Inkscape menu go to File ‣ Properties and in the Page Tab set the Display Units (millimeters), the Orientation to Portrait and Page Size A4: 210.0 x 297.0mm.

        - Import a suitable image by using the menu File ‣ Import. In the menu, go to Path ‣ Trace Bitmap and convert the Object to Path.

        - Go to Extensions ‣ Gcodetools ‣ Tools Libary

        • Select Tools Type: Cylindrical and click Apply.
        • I tried feed speed: 5000mm/s. My CNC plotter worked fast and stable at this speed rate.

        - Go to Extensions ‣ Gcodetools ‣ Orientation Points

        • Orientation type: 2-points mode.
        • Z Surface: 0.0mm. This is the top of your paper surface.
        • Z Depth: -1.0mm. This is working position of Z axis when CNC plotter is drawing object. This negative number ensure that the pen tip can touch the paper.

        - Go to Extensions ‣ Gcodetools ‣ Path to Gcode

        • Z safe height: 3mm. It is height above the plotting surface when moving between drawing points.
        • Click the Path to Gcode Tab before clicking Apply. This creates the G-code file.


        • Open Universal Gcode Platform, select Port and set Baud to 115200, click on Connect tab.
        • Select the appropriate position by moving X axes left - right, Y axes forward - backward and set the original coordinates by button Reset Zero.
        • Click Open ‣ Browse to the G-code file that generated by INKSCAPE.
        • Click Send and CNC plotter will perform drawing picture following the G-code.
        • Monitor the plotter in action on the Visualizer tab.

        And here below is my result:

        Here's a slightly more complicated drawing, a flower. CNC plotter has worked very well.

        We can see CNC plotter operate as the video below. I set it in low speed rate: 500mm/s.

        With flexible coupling diameter 10mm, we can easily change to many kind of pens/ pencils by two small screws.

        You can watch the video below after I changed a different color pen and feed speed has been set to 15000 mm/s.

        Step 14: FINISH

        You can see some pictures of this project. My 3 axis CNC plotter works pretty good but it need to be more fine-tuned and improvement.

        Thank you very much for reading my work and hope you enjoyed my article this time!


        AxiDraw Software 2.6.3 by Evil Mad Scientist Laboratories - This extension fill the classic hatch and crosshatch on selected closed objects in an Inkscape 1.0. It is really useful and now my plotter can draw some better quality images.

        You can check at video below:

        Remix Contest

        Runner Up in the
        Remix Contest