MCU Motor Control
Today’s MCUs can perform precise control of all sorts of systems, even stepper motors. Learn how these two Cornell students built “SCULPT,” a 2.5D carver that takes an image and etches it onto a piece of soap. A Microchip PIC32 MCU controls three stepper motors for each of the X, Y and Z axes, and carves the soap with an end mill attached to a DC motor.
Our project, called Simulacrum Creation Using Linear Production Techniques (SCULPT), carves any image onto a piece of soap with the help of a Microchip PIC32 microcontroller (MCU).
There are three main modes of operation: Serial, Alignment and Movement. Before using the machine, the user selects the image he or she would like carved, and runs a Python script to process the image (Figure 1, top). The script is part of Serial mode, because it uploads the processed image pixel data to the PIC32 via UART before completing.
Once all the pixel data have been transferred, Alignment mode begins. In Alignment mode, the X, Y and Z axes are set to a known initial position, by driving their respective stepper motors until a button placed at the “zero” position is hit. Once aligned, the user can load the soap onto the build plate. After the soap has been loaded, the user triggers the Movement mode by pressing a button on the device, allowing SCULPT to begin carving the image. After the image has been carved into the soap (Figure 1, bottom), the device is ready to begin the cycle again with a new user image. A high-level overview of SCULPT is shown in Figure 2.
The physical components of SCULPT were laser cut, machined or 3D-printed, with all supporting CAD work using Autodesk Fusion 360 . An assembly modeling the final machine is shown in Figure 3. To provide rigidity, the walls, base and legs of SCULPT were laser cut from ¼” acrylic. The top piece connecting the two walls was cut from ⅛” acrylic, because it was not a structural piece. All acrylic and 3D-printed parts were connected with M3 screws.
The X, Y and Z axes used ⅜” diameter steel lead screws and matching ⅜” hex nuts to travel along them. The lead screws and nuts were then housed in 3D-printed components to produce linear translation when the lead screw spun. The lead screws were machined to desired lengths and then modified at the ends to fit properly into housings and to allow for proper set screw contact.
Each axis is made of a stepper motor attached to a lead screw through a rigid coupler. While one Z axis (Z1) uses a lead screw, the other (Z2) is a ½” diameter linear shaft that was machined to length. Matching ½” diameter bronze sleeve bearings were used to travel along this shaft. To provide rigidity, the X and Y axes also have supporting ¼” diameter linear shafts machined to length with matching ¼” diameter bronze sleeve bearings. Linear shafts and bearings were used because their tolerances are both within 0.001″ resulting in better alignment for the X and Y axes while moving and preventing the Z axis from sagging.
There are three NEMA 17 stepper motors—one to drive each of the X, Y and Z axes. Because the Z-axis has more load and weight to move than the X and Y axes, its stepper motor has a higher torque rating than those of the other two axes. While the X and Y axes are rated for 40N-cm (Newton-centimeters), the Z-axis stepper is rated for 59N-cm. As a result, the Z-axis stepper motor is larger than the other two and draws the most current during operation.
The X- and Z-axis limit switches are mounted with M2 screws to the acrylic wall and base plate, respectively. Because the Z switch was too thin to be hit, a 3D-printed adapter was added to the ¼” diameter linear shafts. The Y limit switch was mounted onto the 3D-printed housing for the DC motor. This limit switch was too short to hit its zero position, and also had a 3D-printed adapter added to it to increase its reach.
A PIC32MX250F128B MCU handles all the logic for SCULPT. For this project, the PIC32 was mounted on a small breakout board provided by Cornell’s ECE 4760 course .
The machine uses Pololu DRV8825  motor drivers for each of the X-, Y- and Z-axes stepper motors. This model of motor driver was chosen because: 1) its logic level input had a large range (2.5V to 5.25V, which included the PIC32’s 3.3V output); 2) there was a simple control interface with convenient current-limiting features; and 3) there was a large range for the voltage supply (8.2V to 45V).
The direction of stepper rotation (clockwise or counterclockwise) is managed by a connection from the PIC to the DIR pin on each of the DRV8825 drivers (Figure 4). To prevent excessive current consumption when the driver was not in use, the active-low SLEEP pin was also controlled by the PIC. The final connection from the MCU to the driver was for the STP pin, which allowed to the PIC to manage when each motor was moved. Only full steps were used for the duration of operation, because multiple full steps of the stepper were small enough be the unit step of SCULPT to carve detailed images.
Limit switches were placed at the “zero” positions for alignment. Another button was used to confirm the material has been loaded onto the build plate. The PIC32 has internal pull-ups enabled on all these pins, which are driven low when pressed.
To make the system portable, a 110VAC-to-19VDC power converter was used for power. A large kill switch was added for safety. The 19V input was within the input range for the DRV8825 stepper drivers, but the DC motor spinning the end mill required a smaller voltage to operate. To reduce the voltage even further, a power module based on Texas Instruments’ (TI’s) LM2596 buck converter  was used to regulate the 19V input down to 12V. The regulated output was then appropriate for the TexL298N , a motor driver module based on STMicroelectronics’ L298N chip set, that operated the DC motor.
The DC motor driver also needed 5V logic level inputs rather than 3.3V logic. To accomplish this, a TI 7805  5V voltage regulator and a logic level shifter were used to translate the PIC32’s 3.3V outputs to the appropriate level for the driver. A full electronic schematic for SCULPT is shown in Figure 4.
The MPLABX/XC32 environment from Microchip was used for all programming. The C32 Peripheral Library Guide  and PIC32MX Peripheral Reference Manual abstract many register-level manipulations.
In addition to these libraries, a lightweight C threading library, Protothreads , was used to schedule SCULPT’s operation. This library was slightly modified  to allow for millisecond resolution thread yielding, UART receive threads and DMA-driven UART transmit threads. Using this library was advantageous, because it allowed for a thread to “wait” for at least a specific number of milliseconds, to “yield” to other threads until a variable was a certain value before continuing execution.
For this project, there are three main threads:
protothread_move. At a high level,
protothread_serial receives an image over UART and waits until the material is loaded;
protothread_align finds the zero positions for each axis; and
protothread_move controls the logic for carving the image. A detailed state machine for the interaction of SCULPT’s threads is shown in Figure 5.
A user-created stepper motor struct
stepper_t manages several aspects for each stepper and its driver. They are: which PIC pins were connected to the STP, DIR, and SLEEP pins on the driver; which axis the stepper controls,
stp_num; the current position of an axis,
pos; the direction the motor is moving,
dir_move; and how many steps the motor must still move,
From their zero positions, the stepper motors’ limits of movement were decided by hard-coding maximum and minimum positions for each axis. If a stepper attempted to move beyond its limit, any movement was disabled to prevent the device from damaging itself.
The first stage begins when a user selects an image and processes it with the Python3 script
processImage.py. To cut the image into a user-defined size grid and assign grayscale values to each pixel, the
Python Imaging Library (PIL) and
numpy libraries were used. The X and Y dimensions of the rescaled image can be set by the user, whereas the Z coordinates are set by the image’s grayscale value (0 – 255) for that pixel. While the PIC32 can support images up to 100×100 pixels, the current implementation is limited to 39×47 pixels. This is because the end mill carving the image has a fixed diameter of approximately 2mm that determines the resolution of each step and how many pixels can fit on soap bar. The grid size limitation cannot be improved in software, but it can be improved by making the soap larger or making the end mill smaller.
Once the image has been processed, the data are sent to the PIC32 via a UART connection. The script opens, writes to and closes a serial port, using Python’s
Serial library. First, information about the image is sent. The first transmission for valid data messages of image information was sent with the
char ‘s’ first, followed by the size of the X dimension, size of the Y dimension, and the largest Z coordinate seen in the image (for example,
s 39 47 200 for an X dimension of 39 pixels, a Y dimension of 47 pixels and a maximum grayscale value of 200 for the whole image).
After this first step, pixel data is sent. Valid data messages for a “pixel” were sent with the
char ‘p’ in first, followed by the X index, Y index, and the grayscale value (for example,
p 0 1 27 to encode the (0, 1) pixel with a grayscale value of 27). The transmission is terminated when the
char ‘e’ is sent. The PIC32 handles all input in the
protothread_serial thread at a 9,600 baud rate.
For the script, another Python library called
argparse was used to easily incorporate variable inputs. The user must specify the port of the UART connection with the
-p flag, and the absolute path for the image file with the
-i flag. The user can also optionally turn off debug mode for the script with the -d flag, specify the size of the X dimension with the
-x flag, and specify the size of the Y dimension with the
-y flag. By default, debug mode is turned on, the X dimension is set to its largest value (39), and the Y dimension is set to its largest value (47).
To input an image stored in
/dev/ttyUSB0 with debug mode off, the following command would be typed into the terminal. (The order in which the arguments are given does not matter, but the -p and -i arguments are required.)
./processImage.py -p /dev/tty/USB0 -i /home/user/Downloads/image.png -d
An example image before and after processing is shown in Figure 6. As the image is loaded, SCULPT does not move. Once the image has finished uploading over UART, a green LED lights up, the global variable
data_loaded is set to 1, and the
protothread_serial thread yields until the current image is carved (
image_carved == 1) before being scheduled again.
After the image values have been loaded, SCULPT zeros the three axes by moving each stepper motor until a limit switch is pressed. A pulse duration of 600µs to the DRV8825 drivers’ STP pin was found to be a good rate for each stepper motor to operate at a reasonable speed. This timing management was carried out with the PIC32’s
Timer2 and a user-implemented interrupt service routine (ISR). When
Timer2’s ISR is called, it checks if there are any
stps_left for a stepper and if it is enabled. If so, the STP pin is toggled and the motor’s tracked position pos is updated. When there are no more
stps_left, or if any limits are going to be exceeded, the stepper is disabled.
The motors move until their zero positions are found, and the thread waits until the
material_loaded button is pressed. Before pressing the button, the user should place the soap onto the build plate, and tighten the fasteners to immobilize it. The
protothread_align thread then yields until another image is loaded in
When SCULPT has the image to carve, has aligned its axes, and has material loaded, the stepper motors move according to the image’s pixel coordinates (X and Y axes) and grayscale values (Z-axis) mentioned earlier. Originally, SCULPT directly carved the Z position directly from the grayscale value. This placed considerable force on the end mill if a large, deep line or square were carved. That’s because the end mill would have to go through massive amounts of material sideways. To preclude this issue, the implementation was changed to carve all the locations with a specific Z-layer depth, before incrementing the Z depth.
The carving begins at the uppermost Z height and carves all pixels that have a grayscale value whose final Z height is lower than this uppermost value. After going through the entire image for this Z height, the Z height target is changed to the current Z height + 10 steps further into the material (effectively assigning 10 steps to each of the 255 grayscale levels), and the whole image is carved again. The image is carved by moving sequentially through each column (Y coordinate) for a given row (X coordinate).
Because the image is carved layer by layer, the Z axis has to be reset to its starting position above the soap whenever the next pixel position is not carved. For instance, carving a white line in the middle of a black square requires the Z-axis to carve deep into the soap for the black regions, raise itself to avoid carving or bumping into the white line, and then lower itself to carve the remaining black region. This Z raising is done whenever a new row is being carved, the next pixel’s Y coordinate is more than one spot away and at the end of the carving.
Although all the axes were zeroed in the Alignment phase, the axes drifted while the image was carved. Because of this, each axis had to be re-zeroed during the carving. Since the Y-axis moves the most, it had the worst drift and was re-zeroed after going through each row. The X-axis was recalibrated whenever next pixel’s X-coordinate was more than one position away from the current pixel. The Z-axis was recalibrated every time the X- or Y-axis was calibrated if the Z position corresponded to a grayscale value less than 30 or greater than 120. The latter condition was done because the Z-axis stepper motor position drifted the most when the difference between the target and start position was too small. With all the recalibrating, the carving time was greatly increased, but it could be solved with mechanical fixes, such as replacing all hex nuts with anti-backlash nuts.
Once the current image had been fully carved, SCULPT was ready to accept another image through UART. The Z-axis raised the end mill from the last pixel to its start position, so the user could remove the soap from the build plate. After sending the image over UART, SCULPT moved into Alignment phase again.
A 2.5D carving device was created from the ground up. An example user image and the result from SCULPT is shown in Figure 7. This image took less than a minute to upload via UART and about 4.5 hours to carve.
Despite the success in carving an image, SCULPT could be improved in future iterations. Users found that uploading the image over UART took too long. Although sending small images (such as a 20×20 grid) takes less than 30 seconds to upload, the largest soap image (37×49 pixels) takes about a minute to send. Also, the start position—that is, the (0, 0, 0) coordinate—was found manually and programmed into the software. Having a controller to move, then set the X, Y and Z start positions would be more practical than finding each number by hand.
Another improvement would be using anti-backlash nuts instead of hex nuts for each of the parts traveling on the threaded rods. This would reduce the need for recalibrating while carving the image, make each stepper movement more accurate and speed up the process overall.
Authors’ Note: We thank Matthew Sherman for helping machine many of the parts and providing great suggestions on making SCULPT mechanically stable. We also thank Bruce Land and Joe Skovira for providing helpful electrical and mechanical suggestions.
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • APRIL 2020 #357 – Get a PDF of the issueBecome a Sponsor